Merge branch 'dev_NEW_pro' into dev_NEW_pro_鹤壁
| | |
| | | import request from "@/utils/request"; |
| | | |
| | | export function approveProcessListPage(query) { |
| | | return request({ |
| | | url: '/approveProcess/list', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/list", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function getDept(query) { |
| | | return request({ |
| | | url: '/approveProcess/getDept', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/getDept", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function approveProcessGetInfo(query) { |
| | | return request({ |
| | | url: '/approveProcess/get', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/get", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | // æ°å¢å®¡æ¹æµç¨ |
| | | export function approveProcessAdd(query) { |
| | | return request({ |
| | | url: '/approveProcess/add', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/add", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹å®¡æ¹æµç¨ |
| | | export function approveProcessUpdate(query) { |
| | | return request({ |
| | | url: '/approveProcess/update', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/update", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æäº¤å®¡æ¹ |
| | | export function updateApproveNode(query) { |
| | | return request({ |
| | | url: '/approveNode/updateApproveNode', |
| | | method: 'post', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveNode/updateApproveNode", |
| | | method: "post", |
| | | data: query, |
| | | }); |
| | | } |
| | | // å é¤å®¡æ¹æµç¨ |
| | | export function approveProcessDelete(query) { |
| | | return request({ |
| | | url: '/approveProcess/deleteIds', |
| | | method: 'delete', |
| | | data: query, |
| | | }) |
| | | return request({ |
| | | url: "/approveProcess/deleteIds", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | // æ¥è¯¢å®¡æ¹æµç¨ |
| | | export function approveProcessDetails(query) { |
| | | return request({ |
| | | url: '/approveNode/details/' + query, |
| | | method: 'get', |
| | | }) |
| | | } |
| | | return request({ |
| | | url: "/approveNode/details/" + query, |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // 审æ¹è¯¦æ
|
| | | export function getDeliveryDetailByShippingNo(query) { |
| | | return request({ |
| | | url: "/shippingInfo/getDateilByShippingNo", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | |
| | | import request from "@/utils/request"; |
| | | // å页æ¥è¯¢åºåè®°å½å表 |
| | | export const getStockInventoryListPage = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/pagestockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryListPage = params => { |
| | | return request({ |
| | | url: "/stockInventory/pagestockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å页æ¥è¯¢èååºåè®°å½å表ï¼å
å«ååä¿¡æ¯ï¼ |
| | | export const getStockInventoryListPageCombined = params => { |
| | | return request({ |
| | | url: "/stockInventory/pageListCombinedStockInventory", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å建åºåè®°å½ |
| | | export const createStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/addstockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const createStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/addstockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // åå°åºåè®°å½ |
| | | export const subtractStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/subtractStockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const subtractStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/subtractStockInventory", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/stockInventoryPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryReportList = params => { |
| | | return request({ |
| | | url: "/stockInventory/stockInventoryPage", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryInAndOutReportList = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/stockInAndOutRecord", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | export const getStockInventoryInAndOutReportList = params => { |
| | | return request({ |
| | | url: "/stockInventory/stockInAndOutRecord", |
| | | method: "get", |
| | | params, |
| | | }); |
| | | }; |
| | | |
| | | // å»ç»åºåè®°å½ |
| | | export const frozenStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const frozenStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/frozenStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | // è§£å»åºåè®°å½ |
| | | export const thawStockInventory = (params) => { |
| | | return request({ |
| | | url: "/stockInventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | export const thawStockInventory = params => { |
| | | return request({ |
| | | url: "/stockInventory/thawStock", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | }; |
| | | |
| | | export const getStockInventoryByModelId = productModelId => { |
| | | return request({ |
| | | url: "/stockInventory/getByModelId", |
| | | method: "get", |
| | | params: { productModelId }, |
| | | }); |
| | | }; |
| | |
| | | method: "get", |
| | | }); |
| | | } |
| | | |
| | | // æ¥è¯¢éè´è¯¦æ
|
| | | export function getPurchaseByCode(query) { |
| | | return request({ |
| | | url: "/purchase/ledger/getPurchaseByCode", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function approveProcessGetInfo(query) { |
| | | return request({ |
| | | url: '/approveProcess/get', |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="common-upload"> |
| | | <u-upload |
| | | :fileList="internalFileList" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | :name="name" |
| | | :multiple="multiple" |
| | | :maxCount="maxCount" |
| | | :accept="accept" |
| | | :disabled="disabled" |
| | | ></u-upload> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, watch, onMounted } from 'vue'; |
| | | import { getToken } from "@/utils/auth"; |
| | | import config from "@/config"; |
| | | |
| | | const props = defineProps({ |
| | | // ç¶ç»ä»¶ä¼ å
¥çæä»¶å表ï¼å¯¹åºå端åå¨ç对象åè¡¨ï¼ |
| | | modelValue: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | // æå¤§ä¸ä¼ æ°é |
| | | maxCount: { |
| | | type: Number, |
| | | default: 9 |
| | | }, |
| | | // æ¯å¦æ¯æå¤é |
| | | multiple: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | // æ¥åçæä»¶ç±»å |
| | | accept: { |
| | | type: String, |
| | | default: 'image' |
| | | }, |
| | | // ä¸ä¼ æ¥å£å¯¹åºçåæ°å |
| | | name: { |
| | | type: String, |
| | | default: 'file' |
| | | }, |
| | | // æ¯å¦ç¦ç¨ |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false |
| | | } |
| | | }); |
| | | |
| | | const emit = defineEmits(['update:modelValue']); |
| | | |
| | | // ç¨äº u-upload æ¾ç¤ºçå
é¨å表 |
| | | const internalFileList = ref([]); |
| | | |
| | | // çå¬å¤é¨ modelValue ååï¼åæ¥å°å
鍿¾ç¤ºå表 |
| | | watch(() => props.modelValue, (newVal) => { |
| | | if (newVal) { |
| | | internalFileList.value = newVal.map(item => ({ |
| | | ...item, |
| | | url: item.url || item.previewURL, |
| | | status: 'success', |
| | | message: '' |
| | | })); |
| | | } |
| | | }, { immediate: true, deep: true }); |
| | | |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | // ä¸ä¼ é»è¾ |
| | | const uploadFilePromise = (url) => { |
| | | return new Promise((resolve, reject) => { |
| | | uni.uploadFile({ |
| | | url: config.baseUrl + "/common/upload", |
| | | filePath: url, |
| | | name: "files", // 注æï¼è¿éæ ¹æ®åä»£ç æ¯ "files" |
| | | header: { |
| | | Authorization: "Bearer " + getToken(), |
| | | }, |
| | | success: (res) => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | // 妿è¿åçæ¯æ°ç»ï¼å第ä¸ä¸ªå
ç´ |
| | | const resultData = Array.isArray(data.data) ? data.data[0] : data.data; |
| | | |
| | | // å¤ç url èµå¼ |
| | | if (!resultData.url && resultData.previewURL) { |
| | | resultData.url = resultData.previewURL; |
| | | } |
| | | // å
¼å®¹å代ç ä¸ç name èµå¼ |
| | | if (!resultData.name && resultData.originalFilename) { |
| | | resultData.name = resultData.originalFilename; |
| | | } |
| | | |
| | | resolve(resultData); |
| | | } else { |
| | | reject(data.msg || "ä¸ä¼ 失败"); |
| | | } |
| | | } catch (e) { |
| | | reject("è§£æååºå¤±è´¥"); |
| | | } |
| | | }, |
| | | fail: (err) => { |
| | | reject(err); |
| | | }, |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // ä¸ä¼ åçå¤ç |
| | | const afterRead = async (event) => { |
| | | let lists = [].concat(event.file); |
| | | let currentModelValue = [...props.modelValue]; |
| | | |
| | | // å
å¨å
é¨åè¡¨ä¸æ·»å å ä½ï¼ä¸ä¼ ä¸ç¶æï¼ |
| | | lists.forEach(item => { |
| | | internalFileList.value.push({ |
| | | ...item, |
| | | status: 'uploading', |
| | | message: 'ä¸ä¼ ä¸' |
| | | }); |
| | | }); |
| | | |
| | | for (let i = 0; i < lists.length; i++) { |
| | | try { |
| | | const result = await uploadFilePromise(lists[i].url); |
| | | |
| | | // æ´æ° modelValue |
| | | currentModelValue.push(result); |
| | | emit('update:modelValue', currentModelValue); |
| | | |
| | | } catch (e) { |
| | | // 妿ä¸ä¼ 失败ï¼ä»å
é¨å表ä¸ç§»é¤åææ·»å ç项 |
| | | const errorIndex = internalFileList.value.findIndex(item => item.status === 'uploading'); |
| | | if (errorIndex > -1) { |
| | | internalFileList.value.splice(errorIndex, 1); |
| | | } |
| | | showToast(typeof e === "string" ? e : "ä¸ä¼ 失败"); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // å é¤å¤ç |
| | | const deleteFile = (event) => { |
| | | const newList = [...props.modelValue]; |
| | | newList.splice(event.index, 1); |
| | | emit('update:modelValue', newList); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .common-upload { |
| | | width: 100%; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="approve-page"> |
| | | |
| | | <PageHeader title="å®¡æ ¸" @back="goBack" /> |
| | | |
| | | <PageHeader title="å®¡æ ¸" |
| | | @back="goBack" /> |
| | | <!-- ç³è¯·ä¿¡æ¯ --> |
| | | <view class="application-info"> |
| | | <view class="info-header"> |
| | |
| | | <text class="info-label">ç³è¯·æ¥æ</text> |
| | | <text class="info-value">{{ approvalData.approveTime }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approvalData.approveType === 2"> |
| | | <view class="info-row"> |
| | |
| | | <text class="info-value">{{ approvalData.endDate || '-' }}</text> |
| | | </view> |
| | | </template> |
| | | |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <view v-if="approvalData.approveType === 3" class="info-row"> |
| | | <view v-if="approvalData.approveType === 3" |
| | | class="info-row"> |
| | | <text class="info-label">åºå·®å°ç¹</text> |
| | | <text class="info-value">{{ approvalData.location || '-' }}</text> |
| | | </view> |
| | | |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <view v-if="approvalData.approveType === 4" class="info-row"> |
| | | <view v-if="approvalData.approveType === 4" |
| | | class="info-row"> |
| | | <text class="info-label">æ¥ééé¢</text> |
| | | <text class="info-value">{{ approvalData.price ? `Â¥${approvalData.price}` : '-' }}</text> |
| | | </view> |
| | | </view> |
| | | </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="{ |
| | | <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> |
| | | <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 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> |
| | | <text v-if="step.approveTime" |
| | | class="step-time">{{ step.approveTime }}</text> |
| | | </view> |
| | | |
| | | <view v-if="step.opinion" class="step-opinion"> |
| | | <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;"> |
| | | <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;" /> |
| | | <image :src="step.urlTem" |
| | | mode="widthFix" |
| | | style="width:180px;border-radius:6px;border:1px solid #eee;" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- å®¡æ ¸æè§è¾å
¥ --> |
| | | <view v-if="canApprove" class="approval-input"> |
| | | <view v-if="canApprove" |
| | | class="approval-input"> |
| | | <view class="input-header"> |
| | | <text class="input-title">å®¡æ ¸æè§</text> |
| | | </view> |
| | | |
| | | <view class="input-content"> |
| | | <u-textarea |
| | | v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请è¾å
¥å®¡æ ¸æè§" |
| | | maxlength="200" |
| | | count |
| | | /> |
| | | <u-textarea v-model="approvalOpinion" |
| | | rows="4" |
| | | placeholder="请è¾å
¥å®¡æ ¸æè§" |
| | | maxlength="200" |
| | | count /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- åºé¨æä½æé® --> |
| | | <view v-if="canApprove" class="footer-actions"> |
| | | <u-button class="reject-btn" @click="handleReject">驳å</u-button> |
| | | <u-button class="approve-btn" @click="handleApprove">éè¿</u-button> |
| | | <view v-if="canApprove" |
| | | class="footer-actions"> |
| | | <u-button class="reject-btn" |
| | | @click="handleReject">驳å</u-button> |
| | | <u-button class="approve-btn" |
| | | @click="handleApprove">éè¿</u-button> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, computed } from 'vue' |
| | | import { approveProcessGetInfo, approveProcessDetails, updateApproveNode } from '@/api/collaborativeApproval/approvalProcess' |
| | | import useUserStore from '@/store/modules/user' |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { ref, onMounted, computed } from "vue"; |
| | | import { |
| | | approveProcessGetInfo, |
| | | approveProcessDetails, |
| | | updateApproveNode, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | |
| | | const userStore = useUserStore() |
| | | const approvalData = ref({}) |
| | | const approvalSteps = ref([]) |
| | | const approvalOpinion = ref('') |
| | | const approveId = ref('') |
| | | const userStore = useUserStore(); |
| | | const approvalData = ref({}); |
| | | const approvalSteps = ref([]); |
| | | const approvalOpinion = ref(""); |
| | | const approveId = ref(""); |
| | | |
| | | // ä»è¯¦æ
æ¥å£åæ®µå¯¹é½ canApproveï¼ä»
彿 isShen çèç¹æ¶å¯å®¡æ¹ |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true) |
| | | }) |
| | | // ä»è¯¦æ
æ¥å£åæ®µå¯¹é½ canApproveï¼ä»
彿 isShen çèç¹æ¶å¯å®¡æ¹ |
| | | const canApprove = computed(() => { |
| | | return approvalSteps.value.some(step => step.isShen === true); |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync('approveId') |
| | | if (approveId.value) { |
| | | loadApprovalData() |
| | | } |
| | | }) |
| | | |
| | | const loadApprovalData = () => { |
| | | // åºæ¬ç³è¯·ä¿¡æ¯ |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {} |
| | | }) |
| | | // 审æ¹èç¹è¯¦æ
|
| | | approveProcessDetails(approveId.value).then(res => { |
| | | 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 |
| | | } |
| | | }) |
| | | }) |
| | | } |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync('approveId'); |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | const submitForm = (status) => { |
| | | // å¯éï¼æ ¡éªå®¡æ ¸æè§ |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast('请è¾å
¥å®¡æ ¸æè§') |
| | | return |
| | | } |
| | | // æ¾å°å½åå¯å®¡æ¹èç¹ |
| | | const filteredActivities = activities.value.filter(activity => activity.isShen) |
| | | if (!filteredActivities.length) { |
| | | showToast('å½åæ å¯å®¡æ¹èç¹') |
| | | return |
| | | } |
| | | // åå
¥ç¶æåæè§ |
| | | filteredActivities[0].approveNodeStatus = status |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || '' |
| | | // è®¡ç®æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length - 1 |
| | | // è°ç¨å端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? '审æ¹éè¿' : '审æ¹å·²é©³å' |
| | | showToast(msg) |
| | | // æç¤ºåè¿åä¸ä¸ä¸ªé¡µé¢ |
| | | setTimeout(() => { |
| | | goBack() // å
鍿¯ uni.navigateBack() |
| | | }, 800) |
| | | }) |
| | | } |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: '确认æä½', |
| | | content: 'ç¡®å®è¦éè¿æ¤å®¡æ¹åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(1) |
| | | onMounted(() => { |
| | | approveId.value = uni.getStorageSync("approveId"); |
| | | if (approveId.value) { |
| | | loadApprovalData(); |
| | | } |
| | | }) |
| | | } |
| | | }); |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: '确认æä½', |
| | | content: 'ç¡®å®è¦é©³åæ¤å®¡æ¹åï¼', |
| | | success: (res) => { |
| | | if (res.confirm) submitForm(2) |
| | | const loadApprovalData = () => { |
| | | // åºæ¬ç³è¯·ä¿¡æ¯ |
| | | approveProcessGetInfo({ id: approveId.value }).then(res => { |
| | | approvalData.value = res.data || {}; |
| | | }); |
| | | // 审æ¹èç¹è¯¦æ
|
| | | approveProcessDetails(approveId.value).then(res => { |
| | | 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, |
| | | }; |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync("approveId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const submitForm = status => { |
| | | // å¯éï¼æ ¡éªå®¡æ ¸æè§ |
| | | if (!approvalOpinion.value?.trim()) { |
| | | showToast("请è¾å
¥å®¡æ ¸æè§"); |
| | | return; |
| | | } |
| | | }) |
| | | } |
| | | // åå§èç¹æ°æ®ï¼ç¨äºæäº¤é»è¾ï¼ |
| | | const activities = ref([]) |
| | | // æ¾å°å½åå¯å®¡æ¹èç¹ |
| | | const filteredActivities = activities.value.filter( |
| | | activity => activity.isShen |
| | | ); |
| | | if (!filteredActivities.length) { |
| | | showToast("å½åæ å¯å®¡æ¹èç¹"); |
| | | return; |
| | | } |
| | | // åå
¥ç¶æåæè§ |
| | | filteredActivities[0].approveNodeStatus = status; |
| | | filteredActivities[0].approveNodeReason = approvalOpinion.value || ""; |
| | | // è®¡ç®æ¯å¦ä¸ºæå䏿¥ |
| | | const isLast = |
| | | activities.value.findIndex(a => a.isShen) === activities.value.length - 1; |
| | | // è°ç¨å端 |
| | | updateApproveNode({ ...filteredActivities[0], isLast }).then(() => { |
| | | const msg = status === 1 ? "审æ¹éè¿" : "审æ¹å·²é©³å"; |
| | | showToast(msg); |
| | | // æç¤ºåè¿åä¸ä¸ä¸ªé¡µé¢ |
| | | setTimeout(() => { |
| | | goBack(); // å
鍿¯ uni.navigateBack() |
| | | }, 800); |
| | | }); |
| | | }; |
| | | |
| | | const handleApprove = () => { |
| | | uni.showModal({ |
| | | title: "确认æä½", |
| | | content: "ç¡®å®è¦éè¿æ¤å®¡æ¹åï¼", |
| | | success: res => { |
| | | if (res.confirm) submitForm(1); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleReject = () => { |
| | | uni.showModal({ |
| | | title: "确认æä½", |
| | | content: "ç¡®å®è¦é©³åæ¤å®¡æ¹åï¼", |
| | | success: res => { |
| | | if (res.confirm) submitForm(2); |
| | | }, |
| | | }); |
| | | }; |
| | | // åå§èç¹æ°æ®ï¼ç¨äºæäº¤é»è¾ï¼ |
| | | const activities = ref([]); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | .approve-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 80px; |
| | | } |
| | | } |
| | | |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 16px 20px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | } |
| | | |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .application-info { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .info-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .info-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | .info-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-line { |
| | | display: none; |
| | | .info-row { |
| | | display: flex; |
| | | align-items: center; |
| | | margin-bottom: 12px; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | .info-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | width: 80px; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .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; |
| | | } |
| | | .info-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | flex: 1; |
| | | } |
| | | |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | .process-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | .process-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | .process-steps { |
| | | padding: 20px; |
| | | } |
| | | |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | .process-step { |
| | | display: flex; |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | .step-line { |
| | | display: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | .step-indicator { |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | margin-right: 16px; |
| | | } |
| | | |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .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; |
| | | } |
| | | |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .process-step.completed .step-dot { |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | .step-time { |
| | | font-size: 12px; |
| | | color: #999; |
| | | display: block; |
| | | } |
| | | .process-step.current .step-dot { |
| | | background: #1890ff; |
| | | color: #fff; |
| | | animation: pulse 2s infinite; |
| | | } |
| | | |
| | | .step-opinion { |
| | | background: #f8f9fa; |
| | | padding: 12px; |
| | | border-radius: 8px; |
| | | border-left: 4px solid #52c41a; |
| | | } |
| | | .process-step.pending .step-dot { |
| | | background: #d9d9d9; |
| | | color: #999; |
| | | } |
| | | |
| | | .opinion-label { |
| | | font-size: 12px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | .step-line { |
| | | width: 2px; |
| | | height: 40px; |
| | | background: #d9d9d9; |
| | | margin-top: 8px; |
| | | } |
| | | |
| | | .opinion-content { |
| | | font-size: 14px; |
| | | color: #333; |
| | | line-height: 1.5; |
| | | } |
| | | .process-step.completed .step-line { |
| | | background: #52c41a; |
| | | } |
| | | |
| | | .approval-input { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | .process-step.rejected .step-dot { |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | | } |
| | | .process-step.rejected .step-line { |
| | | background: #ff4d4f; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | .step-content { |
| | | flex: 1; |
| | | padding-top: 4px; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .step-info { |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | .step-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | .step-approver { |
| | | font-size: 14px; |
| | | color: #666; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .reject-btn { |
| | | .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 { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 12px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .input-header { |
| | | padding: 16px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | background: #f8f9fa; |
| | | } |
| | | |
| | | .input-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | |
| | | .input-content { |
| | | padding: 16px; |
| | | } |
| | | |
| | | .footer-actions { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 16px; |
| | | box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .reject-btn { |
| | | width: 120px; |
| | | background: #ff4d4f; |
| | | color: #fff; |
| | |
| | | background: #52c41a; |
| | | color: #fff; |
| | | } |
| | | |
| | | |
| | | /* éé
u-buttonæ ·å¼ */ |
| | | :deep(.u-button) { |
| | | border-radius: 6px; |
| | | } |
| | | |
| | | @keyframes pulse { |
| | | 0% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0.7); |
| | | @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); |
| | | } |
| | | } |
| | | 70% { |
| | | box-shadow: 0 0 0 10px rgba(24, 144, 255, 0); |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | 100% { |
| | | box-shadow: 0 0 0 0 rgba(24, 144, 255, 0); |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | } |
| | | .signature-section { |
| | | background: #fff; |
| | | padding: 12px 16px 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | } |
| | | .signature-header { |
| | | margin-bottom: 8px; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | .signature-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .signature-box { |
| | | width: 100%; |
| | | height: 180px; |
| | | background: #fff; |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | .signature-actions { |
| | | margin-top: 8px; |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <PageHeader title="å®¡æ¹æµç¨" |
| | | <PageHeader :title="operationType === 'detail' ? '详æ
' : 'å®¡æ¹æµç¨'" |
| | | @back="goBack" /> |
| | | <!-- 表ååºå --> |
| | | <u-form ref="formRef" |
| | |
| | | :rules="rules" |
| | | :model="form" |
| | | label-width="140rpx"> |
| | | <u-form-item prop="approveReason" |
| | | label="æµç¨ç¼å·"> |
| | | <u-input v-model="form.approveId" |
| | | disabled |
| | | placeholder="èªå¨ç¼å·" /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveReason" |
| | | :label="approveType === 5 ? 'éè´äºç±' : 'ç³è¯·äºç±'" |
| | | required> |
| | | <u-input v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :placeholder="approveType === 5 ? '请è¾å
¥éè´äºç±' : '请è¾å
¥ç³è¯·äºç±'" |
| | | show-word-limit /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveDeptName" |
| | | label="ç³è¯·é¨é¨" |
| | | required> |
| | | <!-- <u-input v-model="form.approveDeptName" |
| | | <template v-if="operationType !== 'detail'"> |
| | | <u-form-item prop="approveReason" |
| | | label="æµç¨ç¼å·"> |
| | | <u-input v-model="form.approveId" |
| | | disabled |
| | | placeholder="èªå¨ç¼å·" /> |
| | | </u-form-item> |
| | | <u-form-item prop="approveReason" |
| | | :label="approveType === 5 ? 'éè´äºç±' : 'ç³è¯·äºç±'" |
| | | required> |
| | | <u-input v-model="form.approveReason" |
| | | type="textarea" |
| | | rows="2" |
| | | auto-height |
| | | maxlength="200" |
| | | :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 |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="approveUser" |
| | | <u-input v-model="form.approveDeptName" |
| | | readonly |
| | | placeholder="è¯·éæ©ç³è¯·é¨é¨" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- <u-form-item prop="approveUser" |
| | | label="ç³è¯·äºº" |
| | | required> |
| | | <u-input v-model="form.approveUserName" |
| | |
| | | <up-icon name="arrow-right" |
| | | @click="showDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" |
| | | label="å¼å§æ¶é´" |
| | | </u-form-item> --> |
| | | <!-- approveType=2 请åç¸å
³å段 --> |
| | | <template v-if="approveType === 2"> |
| | | <u-form-item prop="startDate" |
| | | label="å¼å§æ¶é´" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | placeholder="请åå¼å§æ¶é´" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" |
| | | label="ç»ææ¶é´" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | placeholder="请åç»ææ¶é´" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="åºå·®å°ç¹" |
| | | required> |
| | | <u-input v-model="form.startDate" |
| | | readonly |
| | | placeholder="请åå¼å§æ¶é´" |
| | | @click="showStartDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showStartDatePicker"></up-icon> |
| | | </template> |
| | | <u-input v-model="form.location" |
| | | placeholder="请è¾å
¥åºå·®å°ç¹" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item prop="endDate" |
| | | label="ç»ææ¶é´" |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 4" |
| | | prop="price" |
| | | label="æ¥ééé¢" |
| | | required> |
| | | <u-input v-model="form.endDate" |
| | | readonly |
| | | placeholder="请åç»ææ¶é´" |
| | | @click="showEndDatePicker" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showEndDatePicker"></up-icon> |
| | | </template> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | clearable /> |
| | | </u-form-item> |
| | | </template> |
| | | <!-- approveType=3 åºå·®ç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 3" |
| | | prop="location" |
| | | label="åºå·®å°ç¹" |
| | | required> |
| | | <u-input v-model="form.location" |
| | | placeholder="请è¾å
¥åºå·®å°ç¹" |
| | | clearable /> |
| | | </u-form-item> |
| | | <!-- approveType=4 æ¥éç¸å
³å段 --> |
| | | <u-form-item v-if="approveType === 4" |
| | | prop="price" |
| | | label="æ¥ééé¢" |
| | | required> |
| | | <u-input v-model="form.price" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ¥ééé¢" |
| | | clearable /> |
| | | <!-- æ¥ä»·å®¡æ¹è¯¦æ
--> |
| | | <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> |
| | | <u-form-item v-if="operationType !== 'detail'" |
| | | label="å¾çéä»¶" |
| | | prop="storageBlobDTOS" |
| | | border-bottom> |
| | | <CommonUpload v-model="form.storageBlobDTOS" /> |
| | | </u-form-item> |
| | | </u-form> |
| | | <!-- éæ©å¨å¼¹çª --> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="éæ©é¨é¨" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åå¼å§æ¶é´éæ©å¨ --> |
| | | <up-popup :show="showStartDate" |
| | | mode="bottom" |
| | | @close="showStartDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åç»ææ¶é´éæ©å¨ --> |
| | | <up-popup :show="showEndDate" |
| | | mode="bottom" |
| | | @close="showEndDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- å®¡æ ¸æµç¨åºå --> |
| | | <view class="approval-process"> |
| | | <view class="approval-header"> |
| | | <text class="approval-title">å®¡æ ¸æµç¨</text> |
| | | <text class="approval-desc">æ¯ä¸ªæ¥éª¤åªè½éæ©ä¸ä¸ªå®¡æ¹äºº</text> |
| | | </view> |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审æ¹äºº</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | | </view> |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | </view> |
| | | <view class="delete-approver-btn" |
| | | @click="removeApprover(stepIndex)">Ã</view> |
| | | </view> |
| | | <view v-else |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">鿩审æ¹äºº</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1" |
| | | @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> |
| | | </view> |
| | | </view> |
| | | <view class="add-step-btn"> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">æ°å¢èç¹</u-button> |
| | | </view> |
| | | </view> |
| | | <template v-if="operationType !== 'detail'"> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="éæ©é¨é¨" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åå¼å§æ¶é´éæ©å¨ --> |
| | | <up-popup :show="showStartDate" |
| | | mode="bottom" |
| | | @close="showStartDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="startDateValue" |
| | | @confirm="onStartDateConfirm" |
| | | @cancel="showStartDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 请åç»ææ¶é´éæ©å¨ --> |
| | | <up-popup :show="showEndDate" |
| | | mode="bottom" |
| | | @close="showEndDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="endDateValue" |
| | | @confirm="onEndDateConfirm" |
| | | @cancel="showEndDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | </template> |
| | | <!-- åºé¨æé® --> |
| | | <view class="footer-btns"> |
| | | <view class="footer-btns" |
| | | v-if="operationType !== 'detail'"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; |
| | | import { |
| | | ref, |
| | | onMounted, |
| | | onUnmounted, |
| | | reactive, |
| | | toRefs, |
| | | computed, |
| | | watch, |
| | | } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { |
| | |
| | | approveProcessGetInfo, |
| | | approveProcessAdd, |
| | | approveProcessUpdate, |
| | | getDeliveryDetailByShippingNo, |
| | | } from "@/api/collaborativeApproval/approvalProcess"; |
| | | import { getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { getPurchaseByCode } from "@/api/procurementManagement/procurementLedger"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | storageBlobDTOS: [], |
| | | startDate: "", |
| | | endDate: "", |
| | | location: "", |
| | |
| | | const productOptions = ref([]); |
| | | const operationType = ref(""); |
| | | const currentApproveStatus = ref(""); |
| | | const approverNodes = ref([]); |
| | | const userList = ref([]); |
| | | const formRef = ref(null); |
| | | const message = ref(""); |
| | | const showDate = ref(false); |
| | |
| | | const endDateValue = 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 fileList = ref([]); |
| | | let nextApproverId = 2; |
| | | const getCurrentinfo = () => { |
| | | userStore.getInfo().then(res => { |
| | | form.value.approveDeptId = res.user.tenantId; |
| | | console.log(res.user.tenantId, "res.user.tenantId"); |
| | | }); |
| | | }; |
| | | |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.approveTime = formatDateToYMD(e.value); |
| | | currentDate.value = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // æ¾ç¤ºè¯·åå¼å§æ¶é´éæ©å¨ |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åå¼å§æ¶é´éæ© |
| | | const onStartDateConfirm = e => { |
| | | form.value.startDate = formatDateToYMD(e.value); |
| | | showStartDate.value = false; |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åç»ææ¶é´éæ© |
| | | const onEndDateConfirm = e => { |
| | | form.value.endDate = formatDateToYMD(e.value); |
| | | 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; |
| | | }); |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // çå¬å®¡æ¹äºç±ååï¼å¦ææ¯ç¹å®å®¡æ¹ç±»ååå°è¯è·å详æ
|
| | | watch( |
| | | () => form.value.approveReason, |
| | | newVal => { |
| | | if (isInitialLoading.value) return; |
| | | if ( |
| | | newVal && |
| | | (isQuotationApproval.value || |
| | | isPurchaseApproval.value || |
| | | isDeliveryApproval.value) |
| | | ) { |
| | | // å»¶è¿ä¸ä¼å请æ±ï¼é¿å
è¾å
¥è¿ç¨ä¸é¢ç¹è§¦å |
| | | debounceFetchDetail(); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | let timer = null; |
| | | const debounceFetchDetail = () => { |
| | | if (timer) clearTimeout(timer); |
| | | timer = setTimeout(() => { |
| | | fetchDetailData(form.value); |
| | | }, 800); |
| | | }; |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | getProductOptions(); |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | form.value.approveUser = userStore.id; |
| | | form.value.approveUserName = userStore.nickName; |
| | | form.value.approveTime = getCurrentDate(); |
| | |
| | | approveType.value = uni.getStorageSync("approveType") || 0; |
| | | |
| | | // 妿æ¯ç¼è¾æ¨¡å¼ï¼ä»æ¬å°åå¨è·åæ°æ® |
| | | if (operationType.value === "edit") { |
| | | if (operationType.value === "edit" || operationType.value === "detail") { |
| | | const storedData = uni.getStorageSync("invoiceLedgerEditRow"); |
| | | if (storedData) { |
| | | const row = JSON.parse(storedData); |
| | | fileList.value = row.commonFileList || []; |
| | | form.value.tempFileIds = fileList.value.map(file => file.id); |
| | | currentApproveStatus.value = row.approveStatus; |
| | | |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }).then( |
| | | res => { |
| | | isInitialLoading.value = true; |
| | | approveProcessGetInfo({ id: row.approveId, approveReason: "1" }) |
| | | .then(res => { |
| | | form.value = { ...res.data }; |
| | | // 忾审æ¹äºº |
| | | if (res.data && res.data.approveUserIds) { |
| | | const userIds = res.data.approveUserIds.split(","); |
| | | approverNodes.value = userIds.map((userId, idx) => { |
| | | const userIdNum = parseInt(userId.trim()); |
| | | // ä»userList䏿¾å°å¯¹åºçç¨æ·ä¿¡æ¯ |
| | | const userInfo = userList.value.find( |
| | | user => user.userId === userIdNum |
| | | ); |
| | | return { |
| | | id: idx + 1, |
| | | userId: userIdNum, |
| | | nickName: userInfo ? userInfo.nickName : null, |
| | | }; |
| | | }); |
| | | nextApproverId = userIds.length + 1; |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼åå§åä¸ä¸ªç©ºç审æ¹èç¹ |
| | | approverNodes.value = [{ id: 1, userId: null, nickName: null }]; |
| | | nextApproverId = 2; |
| | | // 设置å¾çå表æ¾ç¤º |
| | | const fileData = |
| | | res.data.storageBlobVOS || res.data.commonFileList || []; |
| | | if (fileData.length > 0) { |
| | | form.value.storageBlobDTOS = fileData; |
| | | } |
| | | } |
| | | ); |
| | | // è·åé¢å¤è¯¦æ
|
| | | fetchDetailData(res.data); |
| | | }) |
| | | .finally(() => { |
| | | // å»¶è¿ä¸ä¼éç½®ï¼ç¡®ä¿ watch ä¸ä¼è¢«è§¦å |
| | | setTimeout(() => { |
| | | isInitialLoading.value = false; |
| | | }, 100); |
| | | }); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ï¼åå§åä¸ä¸ªç©ºç审æ¹èç¹ |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | } |
| | | |
| | | // çå¬èç³»äººéæ©äºä»¶ |
| | | uni.$on("selectContact", handleSelectContact); |
| | | } catch (error) { |
| | | console.error("è·åé¨é¨æ°æ®å¤±è´¥:", error); |
| | | console.error("è·åæ°æ®å¤±è´¥:", error); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | // ç§»é¤äºä»¶çå¬ |
| | | uni.$off("selectContact", handleSelectContact); |
| | | }); |
| | | onUnmounted(() => {}); |
| | | |
| | | const onConfirm = item => { |
| | | // 设置éä¸çé¨é¨ |
| | |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // æ£æ¥æ¯ä¸ªå®¡æ¹æ¥éª¤æ¯å¦é½æå®¡æ¹äºº |
| | | const hasEmptyStep = approverNodes.value.some(step => !step.nickName); |
| | | if (hasEmptyStep) { |
| | | showToast("请为æ¯ä¸ªå®¡æ¹æ¥éª¤éæ©å®¡æ¹äºº"); |
| | | return; |
| | | } |
| | | |
| | | // æå¨æ£æ¥å¿
å¡«åæ®µï¼é²æ¢å æ°æ®ç±»åé®é¢å¯¼è´çæ ¡éªå¤±è´¥ |
| | | if (!form.value.approveReason || !form.value.approveReason.trim()) { |
| | | showToast("请è¾å
¥ç³è¯·äºç±"); |
| | |
| | | .then(valid => { |
| | | if (valid) { |
| | | // è¡¨åæ ¡éªéè¿ï¼å¯ä»¥æäº¤æ°æ® |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | console.log("approverNodes---", approverNodes.value); |
| | | form.value.approveUserIds = approverNodes.value |
| | | .map(node => node.userId) |
| | | .join(","); |
| | | form.value.approveType = approveType.value; |
| | | form.value.approveDeptId = Number(form.value.approveDeptId); |
| | | // const submitForm = { |
| | | // approveDeptId: form.value.approveDeptId, |
| | | // approveDeptName: form.value.approveDeptName, |
| | | // approveReason: form.value.approveReason, |
| | | // approveTime: form.value.approveTime, |
| | | // approveType: form.value.approveType, |
| | | // approveUser: form.value.approveUser, |
| | | // approveUserIds: form.value.approveUserIds, |
| | | // endDate: form.value.endDate, |
| | | // startDate: form.value.startDate, |
| | | // }; |
| | | // console.log("form.value---", form.value); |
| | | // console.log("submitForm", submitForm); |
| | | |
| | | if (operationType.value === "add" || currentApproveStatus.value == 3) { |
| | | approveProcessAdd(form.value).then(res => { |
| | |
| | | }); |
| | | }; |
| | | |
| | | // å¤çèç³»äººéæ©ç»æ |
| | | const handleSelectContact = data => { |
| | | const { stepIndex, contact } = data; |
| | | // å°éä¸çè系人设置为对åºå®¡æ¹æ¥éª¤ç审æ¹äºº |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | }; |
| | | |
| | | const addApprover = stepIndex => { |
| | | // 跳转å°èç³»äººéæ©é¡µé¢ |
| | | uni.setStorageSync("stepIndex", stepIndex); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", |
| | | }); |
| | | }; |
| | | |
| | | const addApprovalStep = () => { |
| | | // æ·»å æ°çå®¡æ¹æ¥éª¤ |
| | | approverNodes.value.push({ userId: null, nickName: null }); |
| | | }; |
| | | |
| | | const removeApprover = stepIndex => { |
| | | // ç§»é¤å®¡æ¹äºº |
| | | approverNodes.value[stepIndex].userId = null; |
| | | approverNodes.value[stepIndex].nickName = null; |
| | | }; |
| | | |
| | | const removeApprovalStep = stepIndex => { |
| | | // ç¡®ä¿è³å°ä¿çä¸ä¸ªå®¡æ¹æ¥éª¤ |
| | | if (approverNodes.value.length > 1) { |
| | | approverNodes.value.splice(stepIndex, 1); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "è³å°éè¦ä¸ä¸ªå®¡æ¹æ¥éª¤", |
| | | icon: "none", |
| | | }); |
| | | } |
| | | }; |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.approveTime = formatDateToYMD(e.value); |
| | | currentDate.value = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // æ¾ç¤ºè¯·åå¼å§æ¶é´éæ©å¨ |
| | | const showStartDatePicker = () => { |
| | | showStartDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åå¼å§æ¶é´éæ© |
| | | const onStartDateConfirm = e => { |
| | | form.value.startDate = formatDateToYMD(e.value); |
| | | showStartDate.value = false; |
| | | }; |
| | | |
| | | const showEndDatePicker = () => { |
| | | showEndDate.value = true; |
| | | }; |
| | | |
| | | // 确认请åç»ææ¶é´éæ© |
| | | const onEndDateConfirm = e => { |
| | | form.value.endDate = formatDateToYMD(e.value); |
| | | showEndDate.value = false; |
| | | }; |
| | | |
| | | // è·åå½åæ¥æå¹¶æ ¼å¼å为 YYYY-MM-DD |
| | | function getCurrentDate() { |
| | | const today = new Date(); |
| | |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | .approval-header { |
| | | margin-bottom: 16px; |
| | | } |
| | | |
| | | .approval-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .approval-desc { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* æ ·å¼å¢å¼ºä¸ºâç®æ´å°åå飿 ¼â */ |
| | | .approval-steps { |
| | | padding-left: 22px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 11px; |
| | | top: 40px; |
| | | bottom: 40px; |
| | | width: 2px; |
| | | background: linear-gradient( |
| | | to bottom, |
| | | #e6f7ff 0%, |
| | | #bae7ff 50%, |
| | | #91d5ff 100% |
| | | ); |
| | | border-radius: 1px; |
| | | } |
| | | } |
| | | |
| | | .approval-step { |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -18px; |
| | | top: 14px; // ä» 8px è°æ´ä¸º 14pxï¼ä¸æåä¸å¿å¯¹é½ |
| | | width: 12px; |
| | | height: 12px; |
| | | background: #fff; |
| | | border: 3px solid #006cfb; |
| | | border-radius: 50%; |
| | | z-index: 2; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | top: 12px; |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .step-title text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | background: #f0f0f0; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | position: relative; |
| | | line-height: 1.4; // ç¡®ä¿æåè¡é«ä¸è´ |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | gap: 12px; |
| | | position: relative; |
| | | border: 1px solid #e6f7ff; |
| | | box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | width: 48px; |
| | | height: 48px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | | } |
| | | |
| | | .avatar-text { |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .approver-info { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-name { |
| | | display: block; |
| | | font-size: 16px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-dept { |
| | | font-size: 12px; |
| | | color: #999; |
| | | background: rgba(0, 108, 251, 0.05); |
| | | padding: 2px 8px; |
| | | border-radius: 8px; |
| | | display: inline-block; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 2px; |
| | | height: 2px; |
| | | background: #006cfb; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .delete-approver-btn { |
| | | font-size: 16px; |
| | | color: #ff4d4f; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%); |
| | | border: 2px dashed #006cfb; |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | color: #006cfb; |
| | | font-size: 14px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | border: 2px solid #006cfb; |
| | | border-radius: 50%; |
| | | opacity: 0; |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .delete-step-btn { |
| | | color: #ff4d4f; |
| | | font-size: 12px; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | padding: 6px 12px; |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 6px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 4px; |
| | | background: #ff4d4f; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .step-line { |
| | | display: none; // éè忥ç线æ¡ï¼ä½¿ç¨ä¼ªå
ç´ ä»£æ¿ |
| | | } |
| | | |
| | | .add-step-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | .account-detail { |
| | | background-color: #fff; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // å¨ç»å®ä¹ |
| | | @keyframes pulse { |
| | | 0% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | 50% { |
| | | transform: scale(1.2); |
| | | opacity: 0.7; |
| | | } |
| | | 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | } |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes ripple { |
| | | 0% { |
| | | transform: translate(-50%, -50%) scale(0.8); |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translate(-50%, -50%) scale(1.6); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | /* 妿已æ .step-lineï¼è¿éæ´ç²¾åå®ä½å°å·¦ä¾§ä¸å°åç¹å¯¹é½ */ |
| | | .step-line { |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 48px; |
| | | width: 2px; |
| | | height: calc(100% - 48px); |
| | | background: #e5e7eb; |
| | | } |
| | | |
| | | .approver-container { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | gap: 12px; |
| | | padding: 10px 0; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 8px 10px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | position: relative; |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | background: #f3f4f6; |
| | | border: 2px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | animation: none; /* ç¦ç¨æè½¬çå¨ç»ï¼åå½ç®æ´ */ |
| | | } |
| | | |
| | | .avatar-text { |
| | | font-size: 14px; |
| | | color: #374151; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | padding: 0; |
| | | } |
| | | |
| | | .add-approver-btn .add-circle { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: 2px dashed #a0aec0; |
| | | border-radius: 50%; |
| | | color: #6b7280; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 22px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .add-approver-btn .add-label { |
| | | color: #3b82f6; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <view class="actions"> |
| | | <!-- <u-button type="primary" |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn edit" |
| | | :disabled="item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8" |
| | | v-if="!(item.approveStatus == 2 || item.approveStatus == 1 || item.approveStatus == 4 || item.approveStatus == 8 || item.approveType == 5 || item.approveType == 6 || item.approveType == 7)" |
| | | @click="handleItemClick(item)"> |
| | | ç¼è¾ |
| | | </u-button> --> |
| | | </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)"> |
| | | 详æ
|
| | | </u-button> |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn approve" |
| | |
| | | }); |
| | | }; |
| | | |
| | | // æ¥ç详æ
|
| | | const handleDetailClick = item => { |
| | | uni.setStorageSync("invoiceLedgerEditRow", JSON.stringify(item)); |
| | | uni.setStorageSync("operationType", "detail"); |
| | | uni.setStorageSync("approveId", item.approveId); |
| | | uni.setStorageSync("approveType", props.approveType); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/detail", |
| | | }); |
| | | }; |
| | | |
| | | // æ·»å æ°è®°å½ |
| | | const handleAdd = () => { |
| | | uni.setStorageSync("operationType", "add"); |
| | |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="维修项ç®" |
| | | prop="maintenanceProject" |
| | | prop="machineryCategory" |
| | | border-bottom> |
| | | <u-input v-model="form.maintenanceProject" |
| | | <u-input v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥ç»´ä¿®é¡¹ç®" |
| | | clearable /> |
| | | </u-form-item> |
| | |
| | | clearable |
| | | count |
| | | maxlength="200" /> |
| | | </u-form-item> |
| | | <u-form-item label="å¾çéä»¶" |
| | | prop="storageBlobDTOs" |
| | | border-bottom> |
| | | <CommonUpload v-model="form.storageBlobDTOs" /> |
| | | </u-form-item> |
| | | </u-cell-group> |
| | | <!-- æäº¤æé® --> |
| | |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onUnmounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import { onShow, onLoad } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import CommonUpload from "@/components/CommonUpload.vue"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { |
| | | addRepair, |
| | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref("add"); |
| | | const repairId = ref(""); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | |
| | | onLoad(options => { |
| | | if (options.id) { |
| | | repairId.value = options.id; |
| | | } |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | |
| | | repairTime: dayjs().format("YYYY-MM-DD"), // æ¥ä¿®æ¥æ |
| | | repairName: undefined, // æ¥ä¿®äºº |
| | | maintenanceName: undefined, // 维修人 |
| | | maintenanceProject: undefined, // ç»´ä¿®é¡¹ç® |
| | | machineryCategory: undefined, // ç»´ä¿®é¡¹ç® |
| | | remark: undefined, // æ
éç°è±¡ |
| | | storageBlobDTOs: [], // å¾çéä»¶ |
| | | }); |
| | | |
| | | // æ¥ä¿®ç¶æé项 |
| | |
| | | form.value.repairTime = dayjs(data.repairTime).format("YYYY-MM-DD"); |
| | | form.value.repairName = data.repairName; |
| | | form.value.maintenanceName = data.maintenanceName; |
| | | form.value.maintenanceProject = data.maintenanceProject; |
| | | form.value.machineryCategory = data.machineryCategory; |
| | | form.value.remark = data.remark; |
| | | form.value.storageBlobDTOs = data.storageBlobVOs || []; |
| | | repairStatusText.value = |
| | | repairStatusOptions.value.find(item => item.value == data.status) |
| | | ?.name || ""; |
| | |
| | | }; |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | // 页颿¾ç¤ºæ¶é»è¾ |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | // 页é¢å è½½æ¶è·å设å¤å表 |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value }; |
| | | |
| | | const { code } = id |
| | | ? await editRepair({ id: id, ...submitData }) |
| | | : await addRepair(submitData); |
| | |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.removeStorageSync("repairId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 使ç¨uni.getStorageSyncè·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | if (repairId.value) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | // å¯éï¼è·å忏
é¤åå¨çidï¼é¿å
å½±ååç»æä½ |
| | | uni.removeStorageSync("repairId"); |
| | | loadForm(repairId.value); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 使ç¨uni.getStorageSyncè·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | return id; |
| | | return repairId.value; |
| | | }; |
| | | </script> |
| | | |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">维修项ç®</text> |
| | | <text class="detail-value">{{ item.maintenanceProject || '-' }}</text> |
| | | <text class="detail-value">{{ item.machineryCategory || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ
éç°è±¡</text> |
| | |
| | | const edit = id => { |
| | | if (!id) return; |
| | | // 使ç¨uni.setStorageSyncåå¨id |
| | | uni.setStorageSync("repairId", id); |
| | | // uni.setStorageSync("repairId", id); |
| | | uni.navigateTo({ |
| | | url: "/pages/equipmentManagement/repair/add", |
| | | url: "/pages/equipmentManagement/repair/add?id=" + id, |
| | | }); |
| | | }; |
| | | |
| | |
| | | <template> |
| | | <view class="upkeep-add"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader :title="operationType === 'edit' ? 'ç¼è¾ä¿å
»è®¡å' : 'æ°å¢ä¿å
»è®¡å'" @back="goBack" /> |
| | | |
| | | <!-- 表åå
容 --> |
| | | <u-form ref="formRef" :model="form" :rules="formRules" label-width="110px"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <u-form-item label="设å¤åç§°" prop="deviceNameText" required border-bottom> |
| | | <u-input |
| | | v-model="form.deviceNameText" |
| | | placeholder="è¯·éæ©è®¾å¤åç§°" |
| | | readonly |
| | | @click="showDevicePicker" |
| | | clearable |
| | | /> |
| | | <template #right> |
| | | <u-icon name="scan" @click="startScan" class="scan-icon" /> |
| | | </template> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="è§æ ¼åå·" prop="deviceModel" border-bottom> |
| | | <u-input |
| | | v-model="form.deviceModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | readonly |
| | | clearable |
| | | /> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="计åä¿å
»æ¥æ" prop="maintenancePlanTime" required border-bottom> |
| | | <u-input |
| | | v-model="form.maintenancePlanTime" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable |
| | | /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" @click="showDatePicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="ä¿å
»äºº" prop="maintenancePerson" border-bottom> |
| | | <u-input |
| | | v-model="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äºº" |
| | | clearable |
| | | /> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="ä¿å
»é¡¹ç®" prop="maintenanceProject" border-bottom> |
| | | <u-input |
| | | v-model="form.maintenanceProject" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | clearable |
| | | /> |
| | | </u-form-item> |
| | | |
| | | <!-- æäº¤æé® --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" @click="sendForm" :loading="loading">ä¿å</u-button> |
| | | </view> |
| | | </u-form> |
| | | |
| | | <!-- 设å¤éæ©å¨ --> |
| | | <up-action-sheet |
| | | :show="showDevice" |
| | | :actions="deviceActions" |
| | | title="éæ©è®¾å¤" |
| | | @select="onDeviceConfirm" |
| | | @close="showDevice = false" |
| | | /> |
| | | <up-datetime-picker |
| | | :show="showDate" |
| | | v-model="pickerDateValue" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" |
| | | /> |
| | | |
| | | </view> |
| | | <view class="upkeep-add"> |
| | | <!-- 使ç¨éç¨é¡µé¢å¤´é¨ç»ä»¶ --> |
| | | <PageHeader :title="operationType === 'edit' ? 'ç¼è¾ä¿å
»è®¡å' : 'æ°å¢ä¿å
»è®¡å'" |
| | | @back="goBack" /> |
| | | <!-- 表åå
容 --> |
| | | <u-form ref="formRef" |
| | | :model="form" |
| | | :rules="formRules" |
| | | label-width="110px"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <u-form-item label="设å¤åç§°" |
| | | prop="deviceNameText" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.deviceNameText" |
| | | placeholder="è¯·éæ©è®¾å¤åç§°" |
| | | readonly |
| | | @click="showDevicePicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="scan" |
| | | @click="startScan" |
| | | class="scan-icon" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="è§æ ¼åå·" |
| | | prop="deviceModel" |
| | | border-bottom> |
| | | <u-input v-model="form.deviceModel" |
| | | placeholder="请è¾å
¥è§æ ¼åå·" |
| | | readonly |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="计åä¿å
»æ¥æ" |
| | | prop="maintenancePlanTime" |
| | | required |
| | | border-bottom> |
| | | <u-input v-model="form.maintenancePlanTime" |
| | | placeholder="è¯·éæ©è®¡åä¿å
»æ¥æ" |
| | | readonly |
| | | @click="showDatePicker" |
| | | clearable /> |
| | | <template #right> |
| | | <u-icon name="arrow-right" |
| | | @click="showDatePicker" /> |
| | | </template> |
| | | </u-form-item> |
| | | <u-form-item label="ä¿å
»äºº" |
| | | prop="maintenancePerson" |
| | | border-bottom> |
| | | <u-input v-model="form.maintenancePerson" |
| | | placeholder="请è¾å
¥ä¿å
»äºº" |
| | | clearable /> |
| | | </u-form-item> |
| | | <u-form-item label="ä¿å
»é¡¹ç®" |
| | | prop="machineryCategory" |
| | | border-bottom> |
| | | <u-input v-model="form.machineryCategory" |
| | | placeholder="请è¾å
¥ä¿å
»é¡¹ç®" |
| | | clearable /> |
| | | </u-form-item> |
| | | <!-- æäº¤æé® --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" |
| | | @click="sendForm" |
| | | :loading="loading">ä¿å</u-button> |
| | | </view> |
| | | </u-form> |
| | | <!-- 设å¤éæ©å¨ --> |
| | | <up-action-sheet :show="showDevice" |
| | | :actions="deviceActions" |
| | | title="éæ©è®¾å¤" |
| | | @select="onDeviceConfirm" |
| | | @close="showDevice = false" /> |
| | | <up-datetime-picker :show="showDate" |
| | | v-model="pickerDateValue" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onUnmounted } from 'vue'; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import { getDeviceLedger } from '@/api/equipmentManagement/ledger'; |
| | | import { addUpkeep, editUpkeep, getUpkeepById } from '@/api/equipmentManagement/upkeep'; |
| | | import dayjs from "dayjs"; |
| | | import { formatDateToYMD } from '@/utils/ruoyi'; |
| | | import { ref, computed, onMounted, onUnmounted } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { getDeviceLedger } from "@/api/equipmentManagement/ledger"; |
| | | import { |
| | | addUpkeep, |
| | | editUpkeep, |
| | | getUpkeepById, |
| | | } from "@/api/equipmentManagement/upkeep"; |
| | | import dayjs from "dayjs"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»è®¡å表å", |
| | | }); |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | defineOptions({ |
| | | name: "设å¤ä¿å
»è®¡å表å", |
| | | }); |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref('add'); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | const currentDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]); |
| | | // 表åå¼ç¨ |
| | | const formRef = ref(null); |
| | | const operationType = ref("add"); |
| | | const loading = ref(false); |
| | | const showDevice = ref(false); |
| | | const showDate = ref(false); |
| | | const pickerDateValue = ref(Date.now()); |
| | | const currentDate = ref([ |
| | | new Date().getFullYear(), |
| | | new Date().getMonth() + 1, |
| | | new Date().getDate(), |
| | | ]); |
| | | |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | | const deviceNameText = ref(''); |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | const deviceActions = computed(() => { |
| | | return deviceOptions.value.map(item => ({ |
| | | text: item.deviceName, |
| | | value: item.id, |
| | | data: item |
| | | })); |
| | | }); |
| | | // 设å¤é项 |
| | | const deviceOptions = ref([]); |
| | | const deviceNameText = ref(""); |
| | | // 转æ¢ä¸º action-sheet éè¦çæ ¼å¼ |
| | | const deviceActions = computed(() => { |
| | | return deviceOptions.value.map(item => ({ |
| | | text: item.deviceName, |
| | | value: item.id, |
| | | data: item, |
| | | })); |
| | | }); |
| | | |
| | | // æ«ç ç¸å
³ç¶æ |
| | | const isScanning = ref(false); |
| | | const scanTimer = ref(null); |
| | | // æ«ç ç¸å
³ç¶æ |
| | | const isScanning = ref(false); |
| | | const scanTimer = ref(null); |
| | | |
| | | // 表åéªè¯è§å |
| | | const formRules = { |
| | | deviceLedgerId: [{ required: true, trigger: "change", message: "è¯·éæ©è®¾å¤åç§°" }], |
| | | maintenancePlanTime: [{ required: true, trigger: "change", message: "è¯·éæ©è®¡åä¿å
»æ¥æ" }], |
| | | }; |
| | | // 表åéªè¯è§å |
| | | const formRules = { |
| | | deviceLedgerId: [ |
| | | { required: true, trigger: "change", message: "è¯·éæ©è®¾å¤åç§°" }, |
| | | ], |
| | | maintenancePlanTime: [ |
| | | { required: true, trigger: "change", message: "è¯·éæ©è®¡åä¿å
»æ¥æ" }, |
| | | ], |
| | | }; |
| | | |
| | | // ä½¿ç¨ ref 声æè¡¨åæ°æ® |
| | | const form = ref({ |
| | | deviceLedgerId: undefined, // 设å¤ID |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计åä¿å
»æ¥æ |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | maintenanceProject: undefined, // ä¿å
»é¡¹ç® |
| | | }); |
| | | // ä½¿ç¨ ref 声æè¡¨åæ°æ® |
| | | const form = ref({ |
| | | deviceLedgerId: undefined, // 设å¤ID |
| | | deviceModel: undefined, // è§æ ¼åå· |
| | | maintenancePlanTime: dayjs().format("YYYY-MM-DD"), // 计åä¿å
»æ¥æ |
| | | maintenancePerson: undefined, // ä¿å
»äºº |
| | | machineryCategory: undefined, // ä¿å
»é¡¹ç® |
| | | }); |
| | | |
| | | // å 载设å¤å表 |
| | | const loadDeviceName = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (e) { |
| | | showToast('è·å设å¤å表失败'); |
| | | } |
| | | }; |
| | | // å 载设å¤å表 |
| | | const loadDeviceName = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (e) { |
| | | showToast("è·å设å¤å表失败"); |
| | | } |
| | | }; |
| | | |
| | | // å è½½è¡¨åæ°æ®ï¼ç¼è¾æ¨¡å¼ï¼ |
| | | const loadForm = async (id) => { |
| | | if (id) { |
| | | operationType.value = 'edit'; |
| | | try { |
| | | const { code, data } = await getUpkeepById(id); |
| | | if (code == 200) { |
| | | form.value.deviceLedgerId = data.deviceLedgerId; |
| | | form.value.deviceModel = data.deviceModel; |
| | | form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format("YYYY-MM-DD"); |
| | | form.value.maintenancePerson = data.maintenancePerson; |
| | | form.value.maintenanceProject = data.maintenanceProject; |
| | | // 设置设å¤åç§°æ¾ç¤º |
| | | const device = deviceOptions.value.find(item => item.id === data.deviceLedgerId); |
| | | if (device) { |
| | | form.value.deviceNameText = device.deviceName; |
| | | } |
| | | } |
| | | } catch (e) { |
| | | showToast('è·å详æ
失败'); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | operationType.value = 'add'; |
| | | } |
| | | }; |
| | | // å è½½è¡¨åæ°æ®ï¼ç¼è¾æ¨¡å¼ï¼ |
| | | const loadForm = async id => { |
| | | if (id) { |
| | | operationType.value = "edit"; |
| | | try { |
| | | const { code, data } = await getUpkeepById(id); |
| | | if (code == 200) { |
| | | form.value.deviceLedgerId = data.deviceLedgerId; |
| | | form.value.deviceModel = data.deviceModel; |
| | | form.value.maintenancePlanTime = dayjs(data.maintenancePlanTime).format( |
| | | "YYYY-MM-DD" |
| | | ); |
| | | form.value.maintenancePerson = data.maintenancePerson; |
| | | form.value.machineryCategory = data.machineryCategory; |
| | | // 设置设å¤åç§°æ¾ç¤º |
| | | const device = deviceOptions.value.find( |
| | | item => item.id === data.deviceLedgerId |
| | | ); |
| | | if (device) { |
| | | form.value.deviceNameText = device.deviceName; |
| | | } |
| | | } |
| | | } catch (e) { |
| | | showToast("è·å详æ
失败"); |
| | | } |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | operationType.value = "add"; |
| | | } |
| | | }; |
| | | |
| | | // æ«æäºç»´ç åè½ |
| | | const startScan = () => { |
| | | if (isScanning.value) { |
| | | showToast('æ£å¨æ«æä¸ï¼è¯·ç¨å...'); |
| | | return; |
| | | } |
| | | |
| | | // è°ç¨uni-appçæ«ç API |
| | | uni.scanCode({ |
| | | scanType: ['qrCode', 'barCode'], |
| | | success: (res) => { |
| | | handleScanResult(res.result); |
| | | }, |
| | | fail: (err) => { |
| | | console.error('æ«ç 失败:', err); |
| | | showToast('æ«ç 失败ï¼è¯·éè¯'); |
| | | } |
| | | }); |
| | | }; |
| | | // æ«æäºç»´ç åè½ |
| | | const startScan = () => { |
| | | if (isScanning.value) { |
| | | showToast("æ£å¨æ«æä¸ï¼è¯·ç¨å..."); |
| | | return; |
| | | } |
| | | |
| | | // å¤çæ«ç ç»æ |
| | | const handleScanResult = (scanResult) => { |
| | | if (!scanResult) { |
| | | showToast('æ«ç ç»æä¸ºç©º'); |
| | | return; |
| | | } |
| | | |
| | | isScanning.value = true; |
| | | showToast('æ«ç æå'); |
| | | |
| | | // 3ç§åå¤çæ«ç ç»æ |
| | | scanTimer.value = setTimeout(() => { |
| | | processScanResult(scanResult); |
| | | isScanning.value = false; |
| | | }, 1000); |
| | | }; |
| | | function getDeviceIdByRegExp(url) { |
| | | // å¹é
deviceId=åé¢çæ°å |
| | | const reg = /deviceId=(\d+)/; |
| | | const match = url.match(reg); |
| | | // 妿å¹é
å°ç»æï¼è¿åæ°åç±»åï¼å¦åè¿ånull |
| | | return match ? Number(match[1]) : null; |
| | | } |
| | | // å¤çæ«ç ç»æå¹¶å¹é
è®¾å¤ |
| | | const processScanResult = (scanResult) => { |
| | | const deviceId = getDeviceIdByRegExp(scanResult); |
| | | const matchedDevice = deviceOptions.value.find(item => item.id == deviceId); |
| | | |
| | | if (matchedDevice) { |
| | | // æ¾å°å¹é
ç设å¤ï¼èªå¨å¡«å
|
| | | form.value.deviceLedgerId = matchedDevice.id; |
| | | form.value.deviceNameText = matchedDevice.deviceName; |
| | | form.value.deviceModel = matchedDevice.deviceModel; |
| | | showToast('设å¤ä¿¡æ¯å·²èªå¨å¡«å
'); |
| | | } else { |
| | | // æªæ¾å°å¹é
çè®¾å¤ |
| | | showToast('æªæ¾å°å¹é
ç设å¤ï¼è¯·æå¨éæ©'); |
| | | } |
| | | }; |
| | | // è°ç¨uni-appçæ«ç API |
| | | uni.scanCode({ |
| | | scanType: ["qrCode", "barCode"], |
| | | success: res => { |
| | | handleScanResult(res.result); |
| | | }, |
| | | fail: err => { |
| | | console.error("æ«ç 失败:", err); |
| | | showToast("æ«ç 失败ï¼è¯·éè¯"); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | // æ¾ç¤ºè®¾å¤éæ©å¨ |
| | | const showDevicePicker = () => { |
| | | showDevice.value = true; |
| | | }; |
| | | // å¤çæ«ç ç»æ |
| | | const handleScanResult = scanResult => { |
| | | if (!scanResult) { |
| | | showToast("æ«ç ç»æä¸ºç©º"); |
| | | return; |
| | | } |
| | | |
| | | // 确认设å¤éæ© |
| | | const onDeviceConfirm = (selected) => { |
| | | // selected è¿åçæ¯éä¸é¡¹ |
| | | form.value.deviceLedgerId = selected.value; |
| | | form.value.deviceNameText = selected.name; |
| | | const selectedDevice = deviceOptions.value.find(item => item.id === selected.value); |
| | | if (selectedDevice) { |
| | | form.value.deviceModel = selectedDevice.deviceModel; |
| | | } |
| | | showDevice.value = false; |
| | | }; |
| | | isScanning.value = true; |
| | | showToast("æ«ç æå"); |
| | | |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | // 3ç§åå¤çæ«ç ç»æ |
| | | scanTimer.value = setTimeout(() => { |
| | | processScanResult(scanResult); |
| | | isScanning.value = false; |
| | | }, 1000); |
| | | }; |
| | | function getDeviceIdByRegExp(url) { |
| | | // å¹é
deviceId=åé¢çæ°å |
| | | const reg = /deviceId=(\d+)/; |
| | | const match = url.match(reg); |
| | | // 妿å¹é
å°ç»æï¼è¿åæ°åç±»åï¼å¦åè¿ånull |
| | | return match ? Number(match[1]) : null; |
| | | } |
| | | // å¤çæ«ç ç»æå¹¶å¹é
è®¾å¤ |
| | | const processScanResult = scanResult => { |
| | | const deviceId = getDeviceIdByRegExp(scanResult); |
| | | const matchedDevice = deviceOptions.value.find(item => item.id == deviceId); |
| | | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = (e) => { |
| | | form.value.maintenancePlanTime = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | if (matchedDevice) { |
| | | // æ¾å°å¹é
ç设å¤ï¼èªå¨å¡«å
|
| | | form.value.deviceLedgerId = matchedDevice.id; |
| | | form.value.deviceNameText = matchedDevice.deviceName; |
| | | form.value.deviceModel = matchedDevice.deviceModel; |
| | | showToast("设å¤ä¿¡æ¯å·²èªå¨å¡«å
"); |
| | | } else { |
| | | // æªæ¾å°å¹é
çè®¾å¤ |
| | | showToast("æªæ¾å°å¹é
ç设å¤ï¼è¯·æå¨éæ©"); |
| | | } |
| | | }; |
| | | |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | }); |
| | | // æ¾ç¤ºè®¾å¤éæ©å¨ |
| | | const showDevicePicker = () => { |
| | | showDevice.value = true; |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | // 确认设å¤éæ© |
| | | const onDeviceConfirm = selected => { |
| | | // selected è¿åçæ¯éä¸é¡¹ |
| | | form.value.deviceLedgerId = selected.value; |
| | | form.value.deviceNameText = selected.name; |
| | | const selectedDevice = deviceOptions.value.find( |
| | | item => item.id === selected.value |
| | | ); |
| | | if (selectedDevice) { |
| | | form.value.deviceModel = selectedDevice.deviceModel; |
| | | } |
| | | showDevice.value = false; |
| | | }; |
| | | |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | | onUnmounted(() => { |
| | | if (scanTimer.value) { |
| | | clearTimeout(scanTimer.value); |
| | | } |
| | | }); |
| | | // æ¾ç¤ºæ¥æéæ©å¨ |
| | | const showDatePicker = () => { |
| | | showDate.value = true; |
| | | }; |
| | | |
| | | // æäº¤è¡¨å |
| | | const sendForm = async () => { |
| | | try { |
| | | // æå¨éªè¯è¡¨å |
| | | const valid = await formRef.value.validate(); |
| | | if (!valid) return; |
| | | |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value }; |
| | | // ç¡®ä¿æ¥ææ ¼å¼æ£ç¡® |
| | | if (submitData.maintenancePlanTime && !submitData.maintenancePlanTime.includes(':')) { |
| | | submitData.maintenancePlanTime = submitData.maintenancePlanTime + ' 00:00:00'; |
| | | } |
| | | |
| | | const { code } = id |
| | | ? await editUpkeep({ id: id, ...submitData }) |
| | | : await addUpkeep(submitData); |
| | | |
| | | if (code == 200) { |
| | | showToast(`${id ? "ç¼è¾" : "æ°å¢"}计åæå`); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast('表åéªè¯å¤±è´¥'); |
| | | } |
| | | }; |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const onDateConfirm = e => { |
| | | form.value.maintenancePlanTime = formatDateToYMD(e.value); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | // æ¸
é¤åå¨çid |
| | | uni.removeStorageSync('repairId'); |
| | | uni.navigateBack(); |
| | | }; |
| | | onShow(() => { |
| | | // 页颿¾ç¤ºæ¶è·ååæ° |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | const id = uni.getStorageSync('repairId'); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | | } |
| | | }; |
| | | onMounted(() => { |
| | | // 页é¢å è½½æ¶è·å设å¤å表ååæ° |
| | | loadDeviceName(); |
| | | getPageParams(); |
| | | }); |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | return uni.getStorageSync('repairId'); |
| | | }; |
| | | // ç»ä»¶å¸è½½æ¶æ¸
ç宿¶å¨ |
| | | onUnmounted(() => { |
| | | if (scanTimer.value) { |
| | | clearTimeout(scanTimer.value); |
| | | } |
| | | }); |
| | | |
| | | // æäº¤è¡¨å |
| | | const sendForm = async () => { |
| | | try { |
| | | // æå¨éªè¯è¡¨å |
| | | const valid = await formRef.value.validate(); |
| | | if (!valid) return; |
| | | |
| | | loading.value = true; |
| | | const id = getPageId(); |
| | | |
| | | // åå¤æäº¤æ°æ® |
| | | const submitData = { ...form.value }; |
| | | // ç¡®ä¿æ¥ææ ¼å¼æ£ç¡® |
| | | if ( |
| | | submitData.maintenancePlanTime && |
| | | !submitData.maintenancePlanTime.includes(":") |
| | | ) { |
| | | submitData.maintenancePlanTime = |
| | | submitData.maintenancePlanTime + " 00:00:00"; |
| | | } |
| | | |
| | | const { code } = id |
| | | ? await editUpkeep({ id: id, ...submitData }) |
| | | : await addUpkeep(submitData); |
| | | |
| | | if (code == 200) { |
| | | showToast(`${id ? "ç¼è¾" : "æ°å¢"}计åæå`); |
| | | setTimeout(() => { |
| | | uni.navigateBack(); |
| | | }, 1500); |
| | | } else { |
| | | loading.value = false; |
| | | } |
| | | } catch (e) { |
| | | loading.value = false; |
| | | showToast("表åéªè¯å¤±è´¥"); |
| | | } |
| | | }; |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | // æ¸
é¤åå¨çid |
| | | uni.removeStorageSync("repairId"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | // è·å页é¢åæ° |
| | | const getPageParams = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | const id = uni.getStorageSync("repairId"); |
| | | |
| | | // æ ¹æ®æ¯å¦æidåæ°æ¥å¤ææ¯æ°å¢è¿æ¯ç¼è¾ |
| | | if (id) { |
| | | // ç¼è¾æ¨¡å¼ï¼è·å详æ
|
| | | loadForm(id); |
| | | } else { |
| | | // æ°å¢æ¨¡å¼ |
| | | loadForm(); |
| | | } |
| | | }; |
| | | |
| | | // è·å页é¢ID |
| | | const getPageId = () => { |
| | | // 仿¬å°åå¨è·åid |
| | | return uni.getStorageSync("repairId"); |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import '@/static/scss/form-common.scss'; |
| | | .upkeep-add { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | @import "@/static/scss/form-common.scss"; |
| | | .upkeep-add { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0,0,0,0.05); |
| | | z-index: 1000; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: #fff; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 6.375rem; |
| | | background: #C7C9CC; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 6.375rem; |
| | | background: #c7c9cc; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #FFFFFF; |
| | | width: 14rem; |
| | | background: linear-gradient( 140deg, #00BAFF 0%, #006CFB 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3,88,185,0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 14rem; |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // ååºå¼è°æ´ |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | // ååºå¼è°æ´ |
| | | @media (max-width: 768px) { |
| | | .submit-section { |
| | | padding: 12px; |
| | | } |
| | | } |
| | | |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | .tip-text { |
| | | padding: 4px 16px 0 16px; |
| | | font-size: 12px; |
| | | color: #888; |
| | | } |
| | | |
| | | .scan-icon { |
| | | color: #1989fa; |
| | | font-size: 18px; |
| | | margin-left: 8px; |
| | | cursor: pointer; |
| | | } |
| | | .scan-icon { |
| | | color: #1989fa; |
| | | font-size: 18px; |
| | | margin-left: 8px; |
| | | cursor: pointer; |
| | | } |
| | | </style> |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä¿å
»é¡¹ç®</text> |
| | | <text class="detail-value">{{ item.maintenanceProject || '-' }}</text> |
| | | <text class="detail-value">{{ item.machineryCategory || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
ä¿å
»äºº</text> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="record-container"> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥äº§å大类" |
| | | v-model="searchForm.productName" |
| | | @confirm="handleQuery" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <scroll-view scroll-y class="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore"> |
| | | <view v-for="item in tableData" :key="item.id" class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" size="16" color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.productName }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.model }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åä½</text> |
| | | <text class="detail-value">{{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¹å·</text> |
| | | <text class="detail-value">{{ item.batchNo }}</text> |
| | | </view> |
| | | |
| | | <view class="quantity-section"> |
| | | <view class="quantity-box qualified"> |
| | | <text class="q-label">åæ ¼åºå</text> |
| | | <text class="q-value">{{ item.qualifiedQuantity }}</text> |
| | | </view> |
| | | <view class="quantity-box unqualified"> |
| | | <text class="q-label">ä¸åæ ¼åºå</text> |
| | | <text class="q-value">{{ item.unQualifiedQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="quantity-section"> |
| | | <view class="quantity-box locked"> |
| | | <text class="q-label">åæ ¼å»ç»</text> |
| | | <text class="q-value">{{ item.qualifiedLockedQuantity }}</text> |
| | | </view> |
| | | <view class="quantity-box locked"> |
| | | <text class="q-label">ä¸åæ ¼å»ç»</text> |
| | | <text class="q-value">{{ item.unQualifiedLockedQuantity }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">åºåé¢è¦</text> |
| | | <text class="detail-value">{{ item.warnNum }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ´æ°æ¶é´</text> |
| | | <text class="detail-value">{{ item.updateTime }}</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" /> |
| | | </scroll-view> |
| | | <view v-else-if="!loading" class="no-data"> |
| | | <up-empty mode="data" text="ææ åºåæ°æ®"></up-empty> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, onMounted } from 'vue'; |
| | | import { getStockInventoryListPageCombined } from "@/api/inventoryManagement/stockInventory.js"; |
| | | |
| | | const props = defineProps({ |
| | | productId: { |
| | | type: Number, |
| | | required: true |
| | | } |
| | | }); |
| | | |
| | | const tableData = ref([]); |
| | | const loading = ref(false); |
| | | const loadStatus = ref('loadmore'); |
| | | const page = reactive({ current: 1, size: 10 }); |
| | | const total = ref(0); |
| | | const searchForm = reactive({ |
| | | productName: '', |
| | | topParentProductId: props.productId |
| | | }); |
| | | |
| | | const handleQuery = () => { |
| | | page.current = 1; |
| | | tableData.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const getList = () => { |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | loadStatus.value = 'loading'; |
| | | |
| | | getStockInventoryListPageCombined({ |
| | | ...searchForm, |
| | | current: page.current, |
| | | size: page.size |
| | | }).then(res => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | tableData.value = page.current === 1 ? records : [...tableData.value, ...records]; |
| | | total.value = res.data.total; |
| | | loadStatus.value = tableData.value.length >= total.value ? 'nomore' : 'loadmore'; |
| | | }).catch(() => { |
| | | loading.value = false; |
| | | loadStatus.value = 'loadmore'; |
| | | }); |
| | | }; |
| | | |
| | | const loadMore = () => { |
| | | if (loadStatus.value === 'loadmore') { |
| | | page.current++; |
| | | getList(); |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getList(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .record-container { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .search-section { |
| | | padding: 20rpx; |
| | | background-color: #ffffff; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | background-color: #f2f2f2; |
| | | border-radius: 40rpx; |
| | | padding: 0 30rpx; |
| | | height: 80rpx; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | } |
| | | |
| | | .search-text { |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .filter-button { |
| | | padding-left: 20rpx; |
| | | } |
| | | |
| | | .ledger-list { |
| | | flex: 1; |
| | | padding: 20rpx; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .ledger-item { |
| | | background-color: #ffffff; |
| | | border-radius: 16rpx; |
| | | padding: 30rpx; |
| | | margin-bottom: 20rpx; |
| | | box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05); |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20rpx; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .document-icon { |
| | | width: 40rpx; |
| | | height: 40rpx; |
| | | background: linear-gradient(135deg, #2979ff, #1565c0); |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | .item-id { |
| | | font-size: 30rpx; |
| | | font-weight: bold; |
| | | color: #303133; |
| | | } |
| | | |
| | | .item-details { |
| | | .detail-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-bottom: 16rpx; |
| | | font-size: 26rpx; |
| | | |
| | | .detail-label { |
| | | color: #909399; |
| | | } |
| | | |
| | | .detail-value { |
| | | color: #303133; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .quantity-section { |
| | | display: flex; |
| | | gap: 20rpx; |
| | | margin: 20rpx 0; |
| | | |
| | | .quantity-box { |
| | | flex: 1; |
| | | padding: 16rpx; |
| | | border-radius: 8rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .q-label { |
| | | font-size: 22rpx; |
| | | margin-bottom: 8rpx; |
| | | } |
| | | |
| | | .q-value { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | &.qualified { |
| | | background-color: #ecf5ff; |
| | | color: #409eff; |
| | | } |
| | | |
| | | &.unqualified { |
| | | background-color: #fef0f0; |
| | | color: #f56c6c; |
| | | } |
| | | |
| | | &.locked { |
| | | background-color: #f4f4f5; |
| | | color: #909399; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .no-data { |
| | | padding-top: 200rpx; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="app-container"> |
| | | <PageHeader title="åºå管ç" @back="goBack" /> |
| | | <up-tabs :list="tabs" @click="handleTabClick" :current="activeTab"/> |
| | | <swiper class="swiper-box" :current="activeTab" @change="handleSwiperChange"> |
| | | <swiper-item class="swiper-item"> |
| | | <qualified-record /> |
| | | </swiper-item> |
| | | <swiper-item class="swiper-item"> |
| | | <unqualified-record /> |
| | | </swiper-item> |
| | | </swiper> |
| | | <PageHeader title="åºå管ç" |
| | | @back="goBack" /> |
| | | <view v-if="loading" |
| | | class="loading-state"> |
| | | <up-loading-icon text="å è½½ä¸..."></up-loading-icon> |
| | | </view> |
| | | <template v-else> |
| | | <up-tabs :list="tabs" |
| | | @click="handleTabClick" |
| | | :current="activeTab" /> |
| | | <swiper class="swiper-box" |
| | | :current="activeTab" |
| | | @change="handleSwiperChange"> |
| | | <swiper-item class="swiper-item" |
| | | v-for="tab in products" |
| | | :key="tab.id"> |
| | | <record :product-id="tab.id" |
| | | v-if="activeTab === products.indexOf(tab) || initializedTabs.includes(tab.id)" /> |
| | | </swiper-item> |
| | | </swiper> |
| | | </template> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref } from 'vue'; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import QualifiedRecord from "./Qualified.vue"; |
| | | import UnqualifiedRecord from "./Unqualified.vue"; |
| | | import { ref, onMounted } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import Record from "./Record.vue"; |
| | | import { productTreeList } from "@/api/basicData/product.js"; |
| | | |
| | | const activeTab = ref(0); |
| | | const tabs = ref([ |
| | | { name: 'åæ ¼åºå' }, |
| | | { name: 'ä¸åæ ¼åºå' } |
| | | ]); |
| | | const activeTab = ref(0); |
| | | const tabs = ref([]); |
| | | const products = ref([]); |
| | | const loading = ref(false); |
| | | const initializedTabs = ref([]); |
| | | |
| | | const handleTabClick = (item) => { |
| | | activeTab.value = item.index; |
| | | }; |
| | | const handleTabClick = item => { |
| | | activeTab.value = item.index; |
| | | if (!initializedTabs.value.includes(products.value[item.index].id)) { |
| | | initializedTabs.value.push(products.value[item.index].id); |
| | | } |
| | | }; |
| | | |
| | | const handleSwiperChange = (e) => { |
| | | activeTab.value = e.detail.current; |
| | | }; |
| | | const handleSwiperChange = e => { |
| | | const index = e.detail.current; |
| | | activeTab.value = index; |
| | | if (!initializedTabs.value.includes(products.value[index].id)) { |
| | | initializedTabs.value.push(products.value[index].id); |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | const fetchProducts = async () => { |
| | | loading.value = true; |
| | | try { |
| | | const res = await productTreeList(); |
| | | // è¿æ»¤æ ¹èç¹äº§å |
| | | products.value = res |
| | | .filter(item => item.parentId === null) |
| | | .map(({ id, productName }) => ({ id, productName })); |
| | | tabs.value = products.value.map(p => ({ name: p.productName })); |
| | | |
| | | if (products.value.length > 0) { |
| | | activeTab.value = 0; |
| | | initializedTabs.value = [products.value[0].id]; |
| | | } |
| | | } finally { |
| | | loading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | fetchProducts(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .app-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | background-color: #f8f9fa; |
| | | } |
| | | .swiper-box { |
| | | flex: 1; |
| | | } |
| | | .swiper-item { |
| | | height: 100%; |
| | | } |
| | | :deep(.up-tabs) { |
| | | background-color: #fff; |
| | | } |
| | | .app-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | height: 100vh; |
| | | background-color: #f8f9fa; |
| | | } |
| | | .loading-state { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .swiper-box { |
| | | flex: 1; |
| | | } |
| | | .swiper-item { |
| | | height: 100%; |
| | | } |
| | | :deep(.up-tabs) { |
| | | background-color: #fff; |
| | | } |
| | | </style> |
| | |
| | | </up-form-item> |
| | | <up-form-item label="ä¾åºååç§°" |
| | | prop="supplierName" |
| | | required |
| | | > |
| | | required> |
| | | <up-input v-model="form.supplierName" |
| | | readonly |
| | | :disabled="isReadOnly" |
| | |
| | | placeholder="请è¾å
¥" |
| | | disabled /> |
| | | </up-form-item> |
| | | <view class="approval-process"> |
| | | <view class="approval-header"> |
| | | <text class="approval-title">å®¡æ ¸æµç¨</text> |
| | | <text class="approval-desc">æ¯ä¸ªæ¥éª¤åªè½éæ©ä¸ä¸ªå®¡æ¹äºº</text> |
| | | </view> |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审æ¹äºº</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | | </view> |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | </view> |
| | | <view class="delete-approver-btn" |
| | | v-if="!isReadOnly" |
| | | @click="removeApprover(stepIndex)">Ã</view> |
| | | </view> |
| | | <view v-else-if="!isReadOnly" |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">鿩审æ¹äºº</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1 && !isReadOnly" |
| | | @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> |
| | | </view> |
| | | </view> |
| | | <view class="add-step-btn" v-if="!isReadOnly"> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">æ°å¢èç¹</u-button> |
| | | </view> |
| | | </view> |
| | | <up-popup :show="showTimePicker" |
| | | mode="bottom" |
| | | @close="showTimePicker = false"> |
| | |
| | | type="success" |
| | | size="mini" |
| | | plain /> |
| | | <up-tag v-if="item.type==0" |
| | | text="计æ¶" |
| | | type="info" |
| | | size="mini" |
| | | plain /> |
| | | <up-tag v-else |
| | | text="计件" |
| | | type="warning" |
| | | size="mini" |
| | | plain /> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer" |
| | |
| | | <view class="production-accounting"> |
| | | <PageHeader title="çäº§æ ¸ç®" |
| | | @back="goBack" /> |
| | | |
| | | <!-- çéåºå --> |
| | | <view class="filter-section"> |
| | | <view class="date-type-selector"> |
| | |
| | | lineWidth="30" |
| | | lineHeight="3" /> |
| | | </view> |
| | | |
| | | <view class="date-picker-bar" |
| | | @click="showDatePicker = true"> |
| | | <view class="date-display"> |
| | |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æ±æ»å表 --> |
| | | <view class="summary-section" |
| | | v-if="!showDetail"> |
| | |
| | | text="ææ æ±æ»æ°æ®" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æç»å表 (ç¹å»æ±æ»è¡åæ¾ç¤º) --> |
| | | <view class="detail-section" |
| | | v-else> |
| | |
| | | color="#2979ff"></up-icon> |
| | | <text class="back-text">è¿åæ±æ» ({{ currentUserName }})</text> |
| | | </view> |
| | | |
| | | <view class="ledger-list" |
| | | v-if="detailList.length > 0"> |
| | | <view v-for="(item, index) in detailList" |
| | |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">çäº§æ¥æ</text> |
| | | <text class="detail-value">{{ item.schedulingDate || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产人</text> |
| | | <text class="detail-value">{{ item.schedulingUserName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.productModelName }}</text> |
| | | </view> |
| | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产æ°é</text> |
| | | <text class="detail-value">{{ item.quantity }} {{ item.unit }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶(h)</text> |
| | | <text class="detail-value">{{ item.workHour || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶å®é¢</text> |
| | |
| | | text="ææ æç»æ°æ®" /> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-datetime-picker :show="showDatePicker" |
| | | v-model="pickerValue" |
| | |
| | | salesLedgerProductionAccountingListProductionDetails(params) |
| | | .then(res => { |
| | | const records = res.data.records || []; |
| | | detailList.value = isLoadMore ? [...detailList.value, ...records] : records; |
| | | detailList.value = isLoadMore |
| | | ? [...detailList.value, ...records] |
| | | : records; |
| | | page1.total = res.data.total || 0; |
| | | |
| | | if (detailList.value.length >= page1.total) { |
| | |
| | | <template> |
| | | <view class="picking-detail"> |
| | | <PageHeader :title="é¢æè¯¦æ
" |
| | | <PageHeader title="é¢æè¯¦æ
" |
| | | @back="goBack" /> |
| | | <scroll-view scroll-y |
| | | class="detail-list" |
| | |
| | | @click="openProducerPicker" |
| | | suffix-icon="arrow-down" /> |
| | | </u-form-item> |
| | | <!-- å·¥æ¶ --> |
| | | <u-form-item label="å·¥æ¶" |
| | | v-if="form.type == 0" |
| | | prop="workHour"> |
| | | <u-input v-model="form.workHour" |
| | | placeholder="请è¾å
¥å·¥æ¶" |
| | | type="number" /> |
| | | <text class="param-unit">h</text> |
| | | </u-form-item> |
| | | </view> |
| | | <!-- 卿忰åºå --> |
| | | <view class="form-section" |
| | |
| | | import { addProductMain } from "@/api/productionManagement/productionReporting"; |
| | | import { getInfo } from "@/api/login"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { findProcessParamListOrder } from "@/api/productionManagement/productProcessRoute.js"; |
| | | import { |
| | | findProcessParamListOrder, |
| | | listMaterialPickingDetail, |
| | | } from "@/api/productionManagement/productionOrder.js"; |
| | | import { getDicts } from "@/api/system/dict/data"; |
| | | import { formatDateToYMD, parseTime } from "@/utils/ruoyi"; |
| | | |
| | |
| | | scrapQty: "", |
| | | userName: "", |
| | | workOrderId: "", |
| | | productProcessRouteItemId: "", |
| | | userId: "", |
| | | schedulingUserId: "", |
| | | reportWork: "", |
| | | productMainId: null, |
| | | productionOrderRoutingOperationId: "", |
| | | productionOrderId: "", |
| | | workHour: 0, |
| | | type: null, |
| | | paramGroups: {}, |
| | | }); |
| | | |
| | |
| | | userId: form.value.userId, |
| | | userName: form.value.userName, |
| | | productionOperationTaskId: form.value.workOrderId, |
| | | productProcessRouteItemId: form.value.productProcessRouteItemId, |
| | | reportWork: form.value.reportWork, |
| | | productMainId: form.value.productMainId, |
| | | productionOrderRoutingOperationId: |
| | | form.value.productionOrderRoutingOperationId, |
| | | productionOrderId: form.value.productionOrderId, |
| | | workHour: form.value.workHour, |
| | | productionOperationParamList: productionOperationParamList, |
| | | }; |
| | | |
| | |
| | | }); |
| | | }; |
| | | |
| | | onLoad(options => { |
| | | onLoad(async options => { |
| | | console.log(options, "options"); |
| | | if (!options.orderRow) { |
| | | console.log("ä»é¦é¡µè·³è½¬ï¼æ è®¢åæ°æ®"); |
| | |
| | | const orderRow = JSON.parse(decodeURIComponent(options.orderRow)); |
| | | console.log("æé çorderRow:", orderRow); |
| | | |
| | | // åç
§ PC 端é»è¾ï¼æªé¢ææ æ³æ¥å·¥ |
| | | if (orderRow.productionOrderId) { |
| | | try { |
| | | const res = await listMaterialPickingDetail(orderRow.productionOrderId); |
| | | const records = Array.isArray(res.data) |
| | | ? res.data |
| | | : res.data?.records || []; |
| | | if (res.code === 200 && records.length === 0) { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "æªé¢ææ æ³æ¥å·¥", |
| | | showCancel: false, |
| | | success: () => { |
| | | goBack(); |
| | | }, |
| | | }); |
| | | return; |
| | | } |
| | | } catch (error) { |
| | | console.error("æ¥è¯¢é¢æè¯¦æ
失败:", error); |
| | | } |
| | | } |
| | | |
| | | form.value.planQuantity = |
| | | orderRow.planQuantity != null ? String(orderRow.planQuantity) : ""; |
| | | form.value.productProcessRouteItemId = |
| | | orderRow.productProcessRouteItemId || ""; |
| | | form.value.workOrderId = orderRow.id || ""; |
| | | form.value.reportWork = orderRow.reportWork || ""; |
| | | form.value.productMainId = orderRow.productMainId || null; |
| | | form.value.productionOrderRoutingOperationId = |
| | | orderRow.productionOrderRoutingOperationId || ""; |
| | | form.value.productionOrderId = orderRow.productionOrderId || ""; |
| | | form.value.type = orderRow.type; |
| | | |
| | | if (orderRow.type == 0) { |
| | | form.value.workHour = orderRow.workHour || 0; |
| | | } else { |
| | | form.value.workHour = 0; |
| | | } |
| | | |
| | | getInfo().then(res => { |
| | | form.value.userId = res.user.userId; |
| | |
| | | <text class="detail-value highlight">{{ item.nickName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥æ¶(h)</text> |
| | | <text class="detail-value">{{ item.workHour || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå±å·¥åº</text> |
| | | <view class="detail-value"> |
| | | <up-tag :text="item.process || '-'" |
| | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åç¼å·</text> |
| | | <text class="detail-value">{{ item.workOrderNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éå®ååå·</text> |
| | | <text class="detail-value">{{ item.salesContractNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | |
| | | plain |
| | | text="åæ°è¯¦æ
" |
| | | @click="handleShowParams(item)"></up-button> |
| | | <up-button type="error" |
| | | <!-- <up-button type="error" |
| | | size="small" |
| | | plain |
| | | text="å é¤" |
| | | @click="handleDelete(item)"></up-button> |
| | | @click="handleDelete(item)"></up-button> --> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | :key="idx" |
| | | class="detail-item"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥äº§å</text> |
| | | <text class="detail-value font-bold">{{ input.productName }}</text> |
| | | <text class="detail-label">æ¥å·¥åå·</text> |
| | | <text class="detail-value">{{ input.productNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ input.model }}</text> |
| | | <text class="detail-label">æå
¥äº§ååç§°</text> |
| | | <text class="detail-value font-bold">{{ input.productName || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥äº§ååå·</text> |
| | | <text class="detail-value">{{ input.model || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æå
¥æ°é</text> |
| | | <text class="detail-value highlight">{{ input.quantity }} {{ input.unit }}</text> |
| | | <text class="detail-value highlight">{{ input.quantity || 0 }} {{ input.unit || '' }}</text> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | </view> |
| | |
| | | <!-- åºç¡ä¿¡æ¯ --> |
| | | <view class="info-card"> |
| | | <view class="card-title">åºç¡ä¿¡æ¯</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"> |
| | | <text class="label">ç产订åå·</text> |
| | | <text class="value">{{ rowData.productionOrderDto.npsNo || '-' }}</text> |
| | | <view class="base-info"> |
| | | <view class="info-row"> |
| | | <text class="label">ç产订åå·ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.npsNo || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">产ååç§°</text> |
| | | <text class="value">{{ rowData.productionOrderDto.productName || '-' }}</text> |
| | | <view class="info-row"> |
| | | <text class="label">产ååç§°ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.productName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">产åè§æ ¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto.model || '-' }}</text> |
| | | <view class="info-row"> |
| | | <text class="label">è§æ ¼åå·ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.model || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">è®¡åæ°é</text> |
| | | <text class="value">{{ rowData.productionOrderDto.quantity || 0 }} {{ rowData.productionOrderDto.unit || '' }}</text> |
| | | <view class="info-row"> |
| | | <text class="label">è®¡åæ°éï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.quantity || 0 }} {{ rowData.productionOrderDto?.unit }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å½åç¶æ</text> |
| | | <up-tag :text="getStatusText(rowData.productionOrderDto.status)" |
| | | style="width:100rpx" |
| | | :type="getStatusType(rowData.productionOrderDto.status)" |
| | | size="mini" /> |
| | | <view class="info-row"> |
| | | <text class="label">å½åç¶æï¼</text> |
| | | <view class="value"> |
| | | <up-tag :text="getStatusText(rowData.productionOrderDto?.status)" |
| | | :type="getStatusType(rowData.productionOrderDto?.status)" |
| | | size="mini" /> |
| | | </view> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">客æ·åç§°</text> |
| | | <text class="value">{{ rowData.productionOrderDto.customerName || '-' }}</text> |
| | | <view class="info-row"> |
| | | <text class="label">客æ·åç§°ï¼</text> |
| | | <text class="value">{{ rowData.productionOrderDto?.customerName || '-' }}</text> |
| | | </view> |
| | | <view class="info-item"> |
| | | <text class="label">å¼å§æ¥æ</text> |
| | | <text class="value">{{ formatDate(rowData.productionOrderDto.startTime) }}</text> |
| | | <view class="info-row"> |
| | | <text class="label">å¼å§æ¥æï¼</text> |
| | | <text class="value">{{ formatDate(rowData.productionOrderDto?.startTime) }}</text> |
| | | </view> |
| | | <view class="info-item full-width"> |
| | | <text class="label">宿è¿åº¦</text> |
| | | <view class="progress-container"> |
| | | <up-line-progress :percentage="formatProgress(rowData.productionOrderDto.completionStatus)" |
| | | :activeColor="progressColor(rowData.productionOrderDto.completionStatus)" |
| | | height="8"></up-line-progress> |
| | | <text class="progress-text">{{ formatProgress(rowData.productionOrderDto.completionStatus) }}%</text> |
| | | <view class="info-row"> |
| | | <text class="label">宿è¿åº¦ï¼</text> |
| | | <view class="value progress-box"> |
| | | <up-line-progress :percentage="formatProgress(rowData.productionOrderDto?.completionStatus)" |
| | | :activeColor="progressColor(formatProgress(rowData.productionOrderDto?.completionStatus))" |
| | | :showText="true" /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | <text class="value">{{ item.workOrder.productName }} / {{ item.workOrder.model }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">å½åå·¥åºï¼</text> |
| | | <text class="value">{{ item.workOrder.operationName || '-' }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">éæ±/宿ï¼</text> |
| | | <text class="value">{{ item.workOrder.planQuantity }} / {{ item.workOrder.completeQuantity }}</text> |
| | | </view> |
| | | <view class="content-row"> |
| | | <text class="label">æ¥åºæ°éï¼</text> |
| | | <text class="value error-text">{{ item.workOrder.scrapQty || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="card-footer"> |
| | |
| | | class="detail-item"> |
| | | <view class="item-main"> |
| | | <view class="item-row"><text class="label">æ¥å·¥åå·ï¼</text><text class="value">{{ report.productNo }}</text></view> |
| | | <view class="item-row"><text class="label">äº§åºæ°éï¼</text><text class="value">{{ report.quantity || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">æ¥åºæ°éï¼</text><text class="value error-text">{{ report.scrapQty || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">å·¥æ¶(h)ï¼</text><text class="value">{{ report.workHour || 0 }}</text></view> |
| | | <view class="item-row"><text class="label">å建人ï¼</text><text class="value">{{ report.userName }}</text></view> |
| | | <view class="item-row"><text class="label">å建æ¶é´ï¼</text><text class="value">{{ formatDate(report.createTime, '{y}-{m}-{d} {h}:{i}') }}</text></view> |
| | | </view> |
| | |
| | | <view class="input-list-popup"> |
| | | <view v-for="(item, idx) in inputListData" |
| | | :key="idx" |
| | | class="input-item"> |
| | | <view class="input-row"><text class="label">ç©æåç§°ï¼</text><text class="value">{{ item.materialName }}</text></view> |
| | | <view class="input-row"><text class="label">è§æ ¼åå·ï¼</text><text class="value">{{ item.model }}</text></view> |
| | | <view class="input-row"><text class="label">æå
¥æ°éï¼</text><text class="value">{{ item.quantity }} {{ item.unit }}</text></view> |
| | | <view class="input-row"><text class="label">æ¹æ¬¡å·ï¼</text><text class="value">{{ item.batchNo }}</text></view> |
| | | class="quality-record"> |
| | | <view class="record-title">æå
¥è®°å½ {{ idx + 1 }}</view> |
| | | <view class="info-grid"> |
| | | <view class="info-item"><text class="label">æ¥å·¥åå·</text><text class="value">{{ item.productNo || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">æå
¥æ°é</text><text class="value">{{ item.quantity || 0 }} {{ item.unit || '' }}</text></view> |
| | | <view class="info-item full-width"><text class="label">æå
¥äº§ååç§°</text><text class="value">{{ item.productName || '-' }}</text></view> |
| | | <view class="info-item full-width"><text class="label">æå
¥äº§ååå·</text><text class="value">{{ item.model || '-' }}</text></view> |
| | | </view> |
| | | </view> |
| | | <view v-if="!inputListData || inputListData.length === 0" |
| | | class="no-data-minor">{{ inputLoading ? 'å è½½ä¸...' : 'ææ æå
¥è®°å½' }}</view> |
| | |
| | | size="mini" /></view> |
| | | <view class="info-item"><text class="label">æ£éªå</text><text class="value">{{ record.userName }}</text></view> |
| | | <view class="info-item"><text class="label">æ°é</text><text class="value">{{ record.quantity }} {{ record.unit }}</text></view> |
| | | <view class="info-item"><text class="label">æ¥å·¥åå·</text><text class="value">{{ record.reportNo || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">产ååç§°</text><text class="value">{{ record.productName || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">è§æ ¼åå·</text><text class="value">{{ record.model || '-' }}</text></view> |
| | | <view class="info-item"><text class="label">æ£æµåä½</text><text class="value">{{ record.checkCompany || '-' }}</text></view> |
| | | </view> |
| | | <view class="params-table"> |
| | | <view class="table-header"> |
| | | <text class="col">ææ </text> |
| | | <text class="col">åä½</text> |
| | | <text class="col">æ åå¼</text> |
| | | <text class="col">å
æ§å¼</text> |
| | | <text class="col">å®é
å¼</text> |
| | | </view> |
| | | <view v-for="(param, pIdx) in record.inspectParamList" |
| | | :key="pIdx" |
| | | class="table-row"> |
| | | <text class="col">{{ param.parameterItem }}</text> |
| | | <text class="col">{{ param.unit || '-' }}</text> |
| | | <text class="col">{{ param.standardValue }}</text> |
| | | <text class="col">{{ param.controlValue || '-' }}</text> |
| | | <text class="col" |
| | | :class="{ 'error-text': param.testValue != param.standardValue }">{{ param.testValue }}</text> |
| | | </view> |
| | |
| | | workOrder: row.workOrder || {}, |
| | | reports: (row.reportList || []).map(r => ({ |
| | | ...r.reportMain, |
| | | ...(r.reportOutputList ? r.reportOutputList[0] : {}), |
| | | id: r.reportMain.id, |
| | | productionOperationParamList: r.reportParamList || [], |
| | | })), |
| | |
| | | qualityRecords.value = inspects.map(i => ({ |
| | | ...i.inspect, |
| | | reportNo: i.reportNo, |
| | | productName: row.workOrder?.productName || "-", |
| | | model: row.workOrder?.model || "-", |
| | | userName: i.reportMain?.userName || "-", |
| | | inspectParamList: i.inspectParamList || [], |
| | | })); |
| | |
| | | } |
| | | } |
| | | |
| | | .base-info { |
| | | background: #fff; |
| | | padding: 24rpx; |
| | | border-radius: 16rpx; |
| | | margin-bottom: 30rpx; |
| | | |
| | | .info-row { |
| | | margin-bottom: 16rpx; |
| | | font-size: 28rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | |
| | | &:last-child { |
| | | margin-bottom: 0; |
| | | } |
| | | |
| | | .label { |
| | | color: #999; |
| | | min-width: 180rpx; |
| | | } |
| | | .value { |
| | | color: #333; |
| | | flex: 1; |
| | | font-weight: 500; |
| | | |
| | | &.progress-box { |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .info-grid { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | padding: 10rpx 0; |
| | | |
| | | .info-item { |
| | | width: 50%; |
| | | margin-bottom: 20rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | &.full-width { |
| | | width: 100%; |
| | | } |
| | | |
| | | .label { |
| | | font-size: 24rpx; |
| | | color: #999; |
| | | margin-bottom: 4rpx; |
| | | } |
| | | .value { |
| | | font-size: 28rpx; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .popup-content { |
| | | background: #fff; |
| | | padding: 30rpx; |
| | |
| | | </up-form-item> |
| | | <up-form-item label="ææ éæ©" |
| | | prop="testStandardId" |
| | | required |
| | | border-bottom> |
| | | <up-input v-model="testStandardDisplay" |
| | | placeholder="è¯·éæ©ææ " |
| | |
| | | { required: true, message: "è¯·éæ©äº§ååå·", trigger: "change" }, |
| | | ], |
| | | testStandardId: [ |
| | | { required: false, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | { required: true, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | ], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | |
| | | // è·åå·¥åºå表 |
| | | const getprocessList = () => { |
| | | list().then(res => { |
| | | processList.value = res.data; |
| | | }); |
| | | // list().then(res => { |
| | | // processList.value = res.data; |
| | | // }); |
| | | }; |
| | | |
| | | // è·å产åé项 |
| | |
| | | showToast("è¯·éæ©äº§å"); |
| | | return; |
| | | } |
| | | if (!form.value.testStandardId) { |
| | | showToast("è¯·éæ©ææ "); |
| | | return; |
| | | } |
| | | if (!form.value.checkResult) { |
| | | showToast("è¯·éæ©æ£æµç»æ"); |
| | | return; |
| | |
| | | loading.value = true; |
| | | |
| | | form.value.inspectType = 2; |
| | | if (isEdit.value) { |
| | | if (!isEdit.value) { |
| | | tableData.value.forEach(item => { |
| | | delete item.id; |
| | | }); |
| | |
| | | </view> |
| | | <text class="header-title">{{ detailData.productName || '-' }}</text> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(detailData.checkResult)" |
| | | <u-tag v-if="detailData.checkResult" |
| | | :type="getTagType(detailData.checkResult)" |
| | | size="small" |
| | | class="status-tag"> |
| | | {{ detailData.checkResult || '-' }} |
| | |
| | | </view> |
| | | </view> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(item.checkResult)" |
| | | <u-tag v-if="item.checkResult" |
| | | :type="getTagType(item.checkResult)" |
| | | size="mini" |
| | | class="status-tag"> |
| | | {{ item.checkResult }} |
| | |
| | | </view> |
| | | <!-- æä½æé® --> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="primary" |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> --> |
| | | </u-button> |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewDetail(item)"> |
| | | 详æ
|
| | | </u-button> |
| | | <!-- <u-button type="success" |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="info" |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewFileList(item)"> |
| | |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | </view> |
| | | <!-- å页ç»ä»¶ --> |
| | | <!-- æµ®å¨æ°å¢æé® --> |
| | | <!-- <view class="fab-button" |
| | | <view class="fab-button" |
| | | @click="addInspection"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | | color="#ffffff"></up-icon> |
| | | </view> --> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup v-model:show="showDate" |
| | | mode="date" |
| | |
| | | </up-form-item> |
| | | <up-form-item label="ææ éæ©" |
| | | prop="testStandardId" |
| | | required |
| | | border-bottom> |
| | | <up-input v-model="testStandardDisplay" |
| | | placeholder="è¯·éæ©ææ " |
| | |
| | | border-bottom> |
| | | <up-input v-model="form.checkTime" |
| | | placeholder="è¯·éæ©æ£æµæ¥æ" |
| | | readonly /> |
| | | readonly |
| | | @click="showDatePicker" /> |
| | | <!-- <template #right> |
| | | <up-icon name="calendar" |
| | | <up-icon name="arrow-right" |
| | | @click="showDatePicker"></up-icon> |
| | | </template> --> |
| | | </up-form-item> |
| | |
| | | </up-button> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup v-model:show="showDate" |
| | | mode="date" |
| | | :start-year="2020" |
| | | :end-year="2030" |
| | | @confirm="confirmDate" /> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="pickerValue" |
| | | @confirm="confirmDate" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- ä¾åºåéæ© --> |
| | | <up-action-sheet :show="showSupplierSheet" |
| | | :actions="supplierOptions" |
| | |
| | | const loading = ref(false); |
| | | // æ¥æéæ©å¨ |
| | | const showDate = ref(false); |
| | | const pickerValue = ref(Date.now()); |
| | | // ä¾åºåéæ© |
| | | const showSupplierSheet = ref(false); |
| | | // 产åéæ© |
| | |
| | | { required: true, message: "è¯·éæ©äº§ååå·", trigger: "change" }, |
| | | ], |
| | | testStandardId: [ |
| | | { required: false, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | { required: true, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | ], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | // ç¡®è®¤æ¥æéæ© |
| | | const confirmDate = e => { |
| | | form.value.checkTime = dayjs(e.value).format("YYYY-MM-DD"); |
| | | showDate.value = false; |
| | | }; |
| | | |
| | | // éæ©ä¾åºå |
| | |
| | | showToast("è¯·éæ©äº§å"); |
| | | return; |
| | | } |
| | | if (!form.value.testStandardId) { |
| | | showToast("è¯·éæ©ææ "); |
| | | return; |
| | | } |
| | | if (!form.value.checkResult) { |
| | | showToast("è¯·éæ©æ£æµç»æ"); |
| | | return; |
| | |
| | | loading.value = true; |
| | | |
| | | form.value.inspectType = 0; |
| | | if (isEdit.value) { |
| | | if (!isEdit.value) { |
| | | tableData.value.forEach(item => { |
| | | delete item.id; |
| | | }); |
| | |
| | | </view> |
| | | <text class="header-title">{{ detailData.productName || '-' }}</text> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(detailData.checkResult)" |
| | | <u-tag v-if="detailData.checkResult" |
| | | :type="getTagType(detailData.checkResult)" |
| | | size="small" |
| | | class="status-tag"> |
| | | {{ detailData.checkResult || '-' }} |
| | |
| | | </view> |
| | | </view> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(item.checkResult)" |
| | | <u-tag v-if="item.checkResult" |
| | | :type="getTagType(item.checkResult)" |
| | | size="mini" |
| | | class="status-tag"> |
| | | {{ item.checkResult }} |
| | |
| | | </view> |
| | | <!-- æä½æé® --> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="primary" |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> --> |
| | | </u-button> |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewDetail(item)"> |
| | | 详æ
|
| | | </u-button> |
| | | <!-- <u-button type="success" |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="info" |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewFileList(item)"> |
| | |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | </view> |
| | | <!-- å页ç»ä»¶ --> |
| | | <!-- æµ®å¨æ°å¢æé® --> |
| | | <!-- <view class="fab-button" |
| | | <view class="fab-button" |
| | | @click="addInspection"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | | color="#ffffff"></up-icon> |
| | | </view> --> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup v-model:show="showDate" |
| | | mode="date" |
| | |
| | | </up-form-item> |
| | | <up-form-item label="ææ éæ©" |
| | | prop="testStandardId" |
| | | required |
| | | border-bottom> |
| | | <up-input v-model="testStandardDisplay" |
| | | placeholder="è¯·éæ©ææ " |
| | |
| | | </up-button> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup v-model:show="showDate" |
| | | mode="date" |
| | | :start-year="2020" |
| | | :end-year="2030" |
| | | @confirm="confirmDate" /> |
| | | <up-popup :show="showDate" |
| | | mode="bottom" |
| | | @close="showDate = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="pickerValue" |
| | | @confirm="confirmDate" |
| | | @cancel="showDate = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- å·¥åºéæ© --> |
| | | <up-action-sheet :show="showprocessSheet" |
| | | :actions="processOptions" |
| | |
| | | qualityInspectParamInfo, |
| | | qualityInspectDetailByProductId, |
| | | getQualityTestStandardParamByTestStandardId, |
| | | list, |
| | | } from "@/api/qualityManagement/materialInspection.js"; |
| | | import { getProcessList } from "@/api/productionManagement/processManagement.js"; |
| | | import { userListNoPage } from "@/api/system/user.js"; |
| | | |
| | | // æ¾ç¤ºæç¤ºä¿¡æ¯ |
| | |
| | | { required: true, message: "è¯·éæ©äº§ååå·", trigger: "change" }, |
| | | ], |
| | | testStandardId: [ |
| | | { required: false, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | { required: true, message: "è¯·éæ©ææ ", trigger: "change" }, |
| | | ], |
| | | unit: [{ required: false, message: "请è¾å
¥", trigger: "blur" }], |
| | | quantity: [{ required: true, message: "请è¾å
¥", trigger: "blur" }], |
| | |
| | | |
| | | // è·åå·¥åºå表 |
| | | const getprocessList = () => { |
| | | list().then(res => { |
| | | processList.value = res.data; |
| | | getProcessList({ size: -1, current: -1 }).then(res => { |
| | | processList.value = res.data?.records || res.data || []; |
| | | }); |
| | | }; |
| | | |
| | |
| | | showToast("è¯·éæ©äº§å"); |
| | | return; |
| | | } |
| | | if (!form.value.testStandardId) { |
| | | showToast("è¯·éæ©ææ "); |
| | | return; |
| | | } |
| | | if (!form.value.checkResult) { |
| | | showToast("è¯·éæ©æ£æµç»æ"); |
| | | return; |
| | |
| | | loading.value = true; |
| | | |
| | | form.value.inspectType = 1; |
| | | if (isEdit.value) { |
| | | if (!isEdit.value) { |
| | | tableData.value.forEach(item => { |
| | | delete item.id; |
| | | }); |
| | |
| | | </view> |
| | | <text class="header-title">{{ detailData.productName || '-' }}</text> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(detailData.checkResult)" |
| | | <u-tag v-if="detailData.checkResult" |
| | | :type="getTagType(detailData.checkResult)" |
| | | size="small" |
| | | class="status-tag"> |
| | | {{ detailData.checkResult || '-' }} |
| | | {{ detailData.checkResult }} |
| | | </u-tag> |
| | | <u-tag :type="getStateTagType(detailData.inspectState)" |
| | | size="small" |
| | |
| | | </view> |
| | | </view> |
| | | <view class="status-tags"> |
| | | <u-tag :type="getTagType(item.checkResult)" |
| | | <u-tag v-if="item.checkResult" |
| | | :type="getTagType(item.checkResult)" |
| | | size="mini" |
| | | class="status-tag"> |
| | | {{ item.checkResult }} |
| | |
| | | </view> |
| | | <!-- æä½æé® --> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="primary" |
| | | <u-button type="primary" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="startInspection(item)"> |
| | | ç¼è¾ |
| | | </u-button> --> |
| | | </u-button> |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewDetail(item)"> |
| | | 详æ
|
| | | </u-button> |
| | | <!-- <u-button type="success" |
| | | <u-button type="success" |
| | | size="small" |
| | | class="action-btn" |
| | | :disabled="item.inspectState" |
| | | @click.stop="submitInspection(item)"> |
| | | æäº¤ |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <!-- <u-button type="info" |
| | | <u-button type="info" |
| | | size="small" |
| | | class="action-btn" |
| | | @click.stop="viewFileList(item)"> |
| | |
| | | :disabled="item.inspectState || item.checkName !== ''" |
| | | @click.stop="assignInspector(item)"> |
| | | åé
æ£éªå |
| | | </u-button> --> |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | |
| | | </view> |
| | | <!-- å页ç»ä»¶ --> |
| | | <!-- æµ®å¨æ°å¢æé® --> |
| | | <!-- <view class="fab-button" |
| | | <view class="fab-button" |
| | | @click="addInspection"> |
| | | <up-icon name="plus" |
| | | size="24" |
| | | color="#ffffff"></up-icon> |
| | | </view> --> |
| | | </view> |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup v-model:show="showDate" |
| | | mode="date" |
| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <view class="shipment-page"> |
| | | <PageHeader title="åè´§" |
| | | @back="goBack" /> |
| | | <!-- 表ååºå --> |
| | | <u-form ref="formRef" |
| | | @submit="submitForm" |
| | | :rules="rules" |
| | | :model="form" |
| | | label-width="140rpx"> |
| | | <u-form-item prop="typeValue" |
| | | label="åè´§ç±»å" |
| | | required> |
| | | <u-input v-model="typeValue" |
| | | readonly |
| | | placeholder="è¯·éæ©åè´§æ¹å¼" |
| | | @click="showPicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </u-form-item> |
| | | </u-form> |
| | | <!-- éæ©å¨å¼¹çª --> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="productOptions" |
| | | title="åè´§æ¹å¼" |
| | | @select="onConfirm" |
| | | @close="showPicker = false" /> |
| | | <!-- å®¡æ ¸æµç¨åºå --> |
| | | <view class="approval-process"> |
| | | <view class="approval-header"> |
| | | <text class="approval-title">å®¡æ ¸æµç¨</text> |
| | | <text class="approval-desc">æ¯ä¸ªæ¥éª¤åªè½éæ©ä¸ä¸ªå®¡æ¹äºº</text> |
| | | </view> |
| | | <view class="approval-steps"> |
| | | <view v-for="(step, stepIndex) in approverNodes" |
| | | :key="stepIndex" |
| | | class="approval-step"> |
| | | <view class="step-dot"></view> |
| | | <view class="step-title"> |
| | | <text>审æ¹äºº</text> |
| | | <view class="form-container"> |
| | | <up-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="100" |
| | | input-align="right" |
| | | error-message-align="right"> |
| | | <!-- åºæ¬ä¿¡æ¯ --> |
| | | <u-cell-group title="åºæ¬ä¿¡æ¯" |
| | | class="form-section"> |
| | | <up-form-item label="åè´§æ¹å¼" |
| | | prop="type" |
| | | required> |
| | | <up-input v-model="form.type" |
| | | readonly |
| | | placeholder="è¯·éæ©åè´§æ¹å¼" |
| | | @click="showTypePicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTypePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <block v-if="form.type === '货车'"> |
| | | <up-form-item label="å货车ç" |
| | | prop="shippingCarNumber" |
| | | required> |
| | | <up-input v-model="form.shippingCarNumber" |
| | | placeholder="请è¾å
¥å货车çå·" |
| | | clearable /> |
| | | </up-form-item> |
| | | </block> |
| | | <block v-if="form.type === 'å¿«é'"> |
| | | <up-form-item label="å¿«éå
¬å¸" |
| | | prop="expressCompany" |
| | | required> |
| | | <up-input v-model="form.expressCompany" |
| | | placeholder="请è¾å
¥å¿«éå
¬å¸" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å¿«éåå·" |
| | | prop="expressNumber" |
| | | required> |
| | | <up-input v-model="form.expressNumber" |
| | | placeholder="请è¾å
¥å¿«éåå·" |
| | | clearable /> |
| | | </up-form-item> |
| | | </block> |
| | | </u-cell-group> |
| | | <!-- æ¹æ¬¡éæ© --> |
| | | <u-cell-group title="æ¹æ¬¡éæ©" |
| | | class="form-section"> |
| | | <view class="section-header-info"> |
| | | <text class="subtitle">å¾
åè´§æ°é: {{ goOutData.noQuantity || 0 }}</text> |
| | | </view> |
| | | <view class="approver-container"> |
| | | <view v-if="step.nickName" |
| | | class="approver-item"> |
| | | <view class="approver-avatar"> |
| | | <text class="avatar-text">{{ step.nickName.charAt(0) }}</text> |
| | | <view class="status-dot"></view> |
| | | <view v-if="batchList.length === 0" |
| | | class="empty-text"> |
| | | <text>ææ å¯ç¨åºåæ¹æ¬¡</text> |
| | | </view> |
| | | <view v-else |
| | | class="batch-list"> |
| | | <view v-for="(item, index) in batchList" |
| | | :key="index" |
| | | class="batch-card"> |
| | | <view class="batch-header"> |
| | | <text class="batch-no">æ¹å·: {{ item.batchNo }}</text> |
| | | <text class="batch-qty">åºå: {{ getAvailableQty(item) }}</text> |
| | | </view> |
| | | <view class="approver-info"> |
| | | <text class="approver-name">{{ step.nickName }}</text> |
| | | <up-divider></up-divider> |
| | | <view class="batch-body"> |
| | | <up-form-item label="åè´§æ°é"> |
| | | <up-input v-model="item.deliveryQuantity" |
| | | type="digit" |
| | | placeholder="0.00" |
| | | input-align="right" |
| | | @blur="onBatchQtyChange(item)" /> |
| | | </up-form-item> |
| | | </view> |
| | | <view class="delete-approver-btn" |
| | | @click="removeApprover(stepIndex)">Ã</view> |
| | | </view> |
| | | <view v-else |
| | | class="add-approver-btn" |
| | | @click="addApprover(stepIndex)"> |
| | | <view class="add-circle">+</view> |
| | | <text class="add-label">鿩审æ¹äºº</text> |
| | | </view> |
| | | </view> |
| | | <view class="step-line" |
| | | v-if="stepIndex < approverNodes.length - 1"></view> |
| | | <view class="delete-step-btn" |
| | | v-if="approverNodes.length > 1" |
| | | @click="removeApprovalStep(stepIndex)">å é¤èç¹</view> |
| | | </view> |
| | | </view> |
| | | <view class="add-step-btn"> |
| | | <u-button icon="plus" |
| | | plain |
| | | type="primary" |
| | | style="width: 100%" |
| | | @click="addApprovalStep">æ°å¢èç¹</u-button> |
| | | </view> |
| | | </u-cell-group> |
| | | <!-- åè´§å¾ç --> |
| | | <u-cell-group title="åè´§å¾ç" |
| | | class="form-section"> |
| | | <view class="upload-container"> |
| | | <up-upload :fileList="fileList" |
| | | @afterRead="afterRead" |
| | | @delete="deleteFile" |
| | | multiple |
| | | :maxCount="9" |
| | | width="160rpx" |
| | | height="160rpx" /> |
| | | </view> |
| | | </u-cell-group> |
| | | </up-form> |
| | | </view> |
| | | <!-- åºé¨æé® --> |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" |
| | | @click="goBack">åæ¶</u-button> |
| | | <u-button class="save-btn" |
| | | @click="submitForm">åè´§</u-button> |
| | | </view> |
| | | <FooterButtons confirmText="确认åè´§" |
| | | @cancel="goBack" |
| | | @confirm="submitForm" /> |
| | | <!-- åè´§æ¹å¼éæ©å¨ --> |
| | | <up-action-sheet :show="showTypePicker" |
| | | :actions="typeActions" |
| | | title="åè´§æ¹å¼" |
| | | @select="onTypeSelect" |
| | | @close="showTypePicker = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; |
| | | import config from "@/config"; |
| | | import { ref, onMounted, reactive } from "vue"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { addShippingInfo } from "@/api/salesManagement/salesLedger"; |
| | | const showToast = message => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: "none", |
| | | }); |
| | | }; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { getStockInventoryByModelId } from "@/api/inventoryManagement/stockInventory"; |
| | | import { getToken } from "@/utils/auth"; |
| | | |
| | | const data = reactive({ |
| | | form: { |
| | | approveTime: "", |
| | | approveId: "", |
| | | approveUser: "", |
| | | approveUserName: "", |
| | | approveDeptName: "", |
| | | approveDeptId: "", |
| | | approveReason: "", |
| | | checkResult: "", |
| | | tempFileIds: [], |
| | | approverList: [], // æ°å¢å段ï¼å卿æèç¹ç审æ¹äººid |
| | | startDate: "", |
| | | endDate: "", |
| | | location: "", |
| | | price: "", |
| | | }, |
| | | rules: { |
| | | typeValue: [{ required: false, message: "è¯·éæ©", trigger: "change" }], |
| | | }, |
| | | }); |
| | | const { form, rules } = toRefs(data); |
| | | const showPicker = ref(false); |
| | | const productOptions = ref([ |
| | | { |
| | | value: "货车", |
| | | name: "货车", |
| | | }, |
| | | { |
| | | value: "å¿«é", |
| | | name: "å¿«é", |
| | | }, |
| | | ]); |
| | | const operationType = ref(""); |
| | | const currentApproveStatus = ref(""); |
| | | const approverNodes = ref([]); |
| | | const userList = ref([]); |
| | | const formRef = ref(null); |
| | | const approveType = ref(0); |
| | | const goOutData = ref({}); |
| | | const batchList = ref([]); |
| | | const fileList = ref([]); |
| | | const showTypePicker = ref(false); |
| | | const typeActions = [ |
| | | { name: "货车", value: "货车" }, |
| | | { name: "å¿«é", value: "å¿«é" }, |
| | | ]; |
| | | |
| | | const form = reactive({ |
| | | type: "货车", |
| | | shippingCarNumber: "", |
| | | expressCompany: "", |
| | | expressNumber: "", |
| | | }); |
| | | |
| | | const rules = { |
| | | type: [{ required: true, message: "è¯·éæ©åè´§æ¹å¼", trigger: "change" }], |
| | | shippingCarNumber: [ |
| | | { |
| | | required: true, |
| | | validator: (rule, value, callback) => { |
| | | if (form.type === "货车" && !value) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | message: "请è¾å
¥è½¦çå·", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | expressCompany: [ |
| | | { |
| | | required: true, |
| | | validator: (rule, value, callback) => { |
| | | if (form.type === "å¿«é" && !value) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | message: "请è¾å
¥å¿«éå
¬å¸", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | expressNumber: [ |
| | | { |
| | | required: true, |
| | | validator: (rule, value, callback) => { |
| | | if (form.type === "å¿«é" && !value) { |
| | | return false; |
| | | } |
| | | return true; |
| | | }, |
| | | message: "请è¾å
¥å¿«éåå·", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const formRef = ref(null); |
| | | |
| | | onMounted(async () => { |
| | | try { |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | // 仿¬å°åå¨è·åå货详æ
|
| | | goOutData.value = JSON.parse(uni.getStorageSync("goOutData")); |
| | | console.log(goOutData.value, "goOutData.value"); |
| | | |
| | | // åå§åå®¡æ¹æµç¨èç¹ï¼é»è®¤ä¸ä¸ªèç¹ |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | |
| | | // çå¬èç³»äººéæ©äºä»¶ |
| | | uni.$on("selectContact", handleSelectContact); |
| | | } catch (error) { |
| | | console.error("è·å失败:", error); |
| | | const storedData = uni.getStorageSync("goOutData"); |
| | | goOutData.value = JSON.parse(storedData || "{}"); |
| | | if (goOutData.value.productModelId) { |
| | | loadBatches(goOutData.value.productModelId); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | // ç§»é¤äºä»¶çå¬ |
| | | uni.$off("selectContact", handleSelectContact); |
| | | }); |
| | | const typeValue = ref("货车"); |
| | | const onConfirm = item => { |
| | | // 设置éä¸çé¨é¨ |
| | | typeValue.value = item.name; |
| | | showPicker.value = false; |
| | | const loadBatches = async modelId => { |
| | | if (!modelId) return; |
| | | try { |
| | | const res = await getStockInventoryByModelId(modelId); |
| | | const rawList = Array.isArray(res?.data) |
| | | ? res.data |
| | | : res?.data?.records || res?.data?.rows || res || []; |
| | | const seenIds = new Set(); |
| | | batchList.value = rawList |
| | | .filter(item => { |
| | | if (!item?.id || !item?.batchNo || seenIds.has(item.id)) { |
| | | return false; |
| | | } |
| | | seenIds.add(item.id); |
| | | return true; |
| | | }) |
| | | .map(item => ({ |
| | | ...item, |
| | | deliveryQuantity: "", |
| | | })); |
| | | } catch (e) { |
| | | console.error("å è½½æ¹æ¬¡å¤±è´¥", e); |
| | | } |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | // æ¸
餿¬å°åå¨çæ°æ® |
| | | uni.removeStorageSync("operationType"); |
| | | uni.removeStorageSync("invoiceLedgerEditRow"); |
| | | uni.removeStorageSync("approveType"); |
| | | uni.navigateBack(); |
| | | const getAvailableQty = item => { |
| | | const quantity = |
| | | item?.qualitity ?? |
| | | item?.quantity ?? |
| | | item?.unLockedQuantity ?? |
| | | item?.qualifiedUnLockedQuantity ?? |
| | | item?.qualifiedQuantity ?? |
| | | item?.stockQuantity; |
| | | return quantity ?? 0; |
| | | }; |
| | | |
| | | const submitForm = () => { |
| | | // æ£æ¥æ¯ä¸ªå®¡æ¹æ¥éª¤æ¯å¦é½æå®¡æ¹äºº |
| | | const hasEmptyStep = approverNodes.value.some(step => !step.nickName); |
| | | if (hasEmptyStep) { |
| | | showToast("请为æ¯ä¸ªå®¡æ¹æ¥éª¤éæ©å®¡æ¹äºº"); |
| | | const onBatchQtyChange = item => { |
| | | const val = parseFloat(item.deliveryQuantity); |
| | | if (isNaN(val) || val <= 0) { |
| | | item.deliveryQuantity = ""; |
| | | return; |
| | | } |
| | | formRef.value |
| | | .validate() |
| | | .then(valid => { |
| | | if (valid) { |
| | | // è¡¨åæ ¡éªéè¿ï¼å¯ä»¥æäº¤æ°æ® |
| | | // æ¶éææèç¹ç审æ¹äººid |
| | | console.log("approverNodes---", approverNodes.value); |
| | | const approveUserIds = approverNodes.value |
| | | .map(node => node.userId) |
| | | .join(","); |
| | | const params = { |
| | | salesLedgerId: goOutData.value.salesLedgerId, |
| | | salesLedgerProductId: goOutData.value.id, |
| | | type: typeValue.value, |
| | | approveUserIds, |
| | | }; |
| | | console.log(params, "params"); |
| | | |
| | | addShippingInfo(params).then(res => { |
| | | showToast("åè´§æå"); |
| | | setTimeout(() => { |
| | | goBack(); |
| | | }, 500); |
| | | }); |
| | | } |
| | | }) |
| | | .catch(error => { |
| | | console.error("è¡¨åæ ¡éªå¤±è´¥:", error); |
| | | // å°è¯è·åå
·ä½çéè¯¯åæ®µ |
| | | if (error && error.errors) { |
| | | const firstError = error.errors[0]; |
| | | if (firstError) { |
| | | uni.showToast({ |
| | | title: firstError.message || "è¡¨åæ ¡éªå¤±è´¥ï¼è¯·æ£æ¥å¿
填项", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | const available = getAvailableQty(item); |
| | | if (val > available) { |
| | | uni.showToast({ title: "ä¸è½è¶
è¿åºåæ°é", icon: "none" }); |
| | | item.deliveryQuantity = available.toString(); |
| | | } |
| | | |
| | | const totalToShip = Number(goOutData.value.noQuantity || 0); |
| | | const otherBatchesTotal = batchList.value.reduce((sum, b) => { |
| | | if (b.id === item.id) return sum; |
| | | return sum + Number(b.deliveryQuantity || 0); |
| | | }, 0); |
| | | |
| | | if (val + otherBatchesTotal > totalToShip) { |
| | | uni.showToast({ title: "æ»æ°ä¸è½è¶
è¿å¾
åè´§æ°é", icon: "none" }); |
| | | item.deliveryQuantity = (totalToShip - otherBatchesTotal).toString(); |
| | | } |
| | | }; |
| | | |
| | | const onTypeSelect = item => { |
| | | form.type = item.name; |
| | | showTypePicker.value = false; |
| | | }; |
| | | |
| | | const afterRead = async event => { |
| | | const { file } = event; |
| | | const lists = [].concat(file); |
| | | const token = getToken(); |
| | | |
| | | for (let i = 0; i < lists.length; i++) { |
| | | const item = lists[i]; |
| | | const uploadIndex = fileList.value.length; |
| | | fileList.value.push({ |
| | | ...item, |
| | | status: "uploading", |
| | | message: "ä¸ä¼ ä¸", |
| | | }); |
| | | |
| | | uni.uploadFile({ |
| | | url: config.baseUrl + "/common/upload", |
| | | filePath: item.url, |
| | | name: "files", |
| | | header: { |
| | | Authorization: "Bearer " + token, |
| | | }, |
| | | success: res => { |
| | | try { |
| | | const data = JSON.parse(res.data); |
| | | if (data.code === 200) { |
| | | const fileData = Array.isArray(data.data) |
| | | ? data.data[0] |
| | | : data.data || data; |
| | | fileList.value[uploadIndex].status = "success"; |
| | | fileList.value[uploadIndex].message = ""; |
| | | fileList.value[uploadIndex].url = fileData.url; |
| | | fileList.value[uploadIndex].storageBlobDTO = fileData; |
| | | } else { |
| | | fileList.value[uploadIndex].status = "failed"; |
| | | fileList.value[uploadIndex].message = data.msg || "ä¸ä¼ 失败"; |
| | | } |
| | | } catch (e) { |
| | | fileList.value[uploadIndex].status = "failed"; |
| | | fileList.value[uploadIndex].message = "è§£æå¤±è´¥"; |
| | | } |
| | | } |
| | | // æ¾ç¤ºéç¨éè¯¯ä¿¡æ¯ |
| | | uni.showToast({ |
| | | title: "è¡¨åæ ¡éªå¤±è´¥ï¼è¯·æ£æ¥å¿
填项", |
| | | icon: "none", |
| | | }); |
| | | }, |
| | | fail: () => { |
| | | fileList.value[uploadIndex].status = "failed"; |
| | | fileList.value[uploadIndex].message = "ç½ç»å¼å¸¸"; |
| | | }, |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | // å¤çèç³»äººéæ©ç»æ |
| | | const handleSelectContact = data => { |
| | | const { stepIndex, contact } = data; |
| | | // å°éä¸çè系人设置为对åºå®¡æ¹æ¥éª¤ç审æ¹äºº |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | const deleteFile = event => { |
| | | fileList.value.splice(event.index, 1); |
| | | }; |
| | | |
| | | const addApprover = stepIndex => { |
| | | // 跳转å°èç³»äººéæ©é¡µé¢ |
| | | uni.setStorageSync("stepIndex", stepIndex); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", |
| | | }); |
| | | }; |
| | | const goBack = () => uni.navigateBack(); |
| | | |
| | | const addApprovalStep = () => { |
| | | // æ·»å æ°çå®¡æ¹æ¥éª¤ |
| | | approverNodes.value.push({ userId: null, nickName: null }); |
| | | }; |
| | | const submitForm = async () => { |
| | | const valid = await formRef.value.validate().catch(() => false); |
| | | if (!valid) return; |
| | | |
| | | const removeApprover = stepIndex => { |
| | | // ç§»é¤å®¡æ¹äºº |
| | | approverNodes.value[stepIndex].userId = null; |
| | | approverNodes.value[stepIndex].nickName = null; |
| | | }; |
| | | const selectedBatches = batchList.value.filter( |
| | | b => parseFloat(b.deliveryQuantity) > 0 |
| | | ); |
| | | if (selectedBatches.length === 0) { |
| | | uni.showToast({ title: "请è³å°å¡«åä¸ä¸ªæ¹æ¬¡çåè´§æ°é", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | const removeApprovalStep = stepIndex => { |
| | | // ç¡®ä¿è³å°ä¿çä¸ä¸ªå®¡æ¹æ¥éª¤ |
| | | if (approverNodes.value.length > 1) { |
| | | approverNodes.value.splice(stepIndex, 1); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "è³å°éè¦ä¸ä¸ªå®¡æ¹æ¥éª¤", |
| | | icon: "none", |
| | | }); |
| | | // Check if any file is still uploading |
| | | if (fileList.value.some(f => f.status === "uploading")) { |
| | | uni.showToast({ title: "请çå¾
å¾çä¸ä¼ 宿", icon: "none" }); |
| | | return; |
| | | } |
| | | |
| | | const payload = { |
| | | salesLedgerId: goOutData.value.salesLedgerId, |
| | | salesLedgerProductId: goOutData.value.id, |
| | | type: form.type, |
| | | shippingCarNumber: form.type === "货车" ? form.shippingCarNumber : "", |
| | | expressCompany: form.type === "å¿«é" ? form.expressCompany : "", |
| | | expressNumber: form.type === "å¿«é" ? form.expressNumber : "", |
| | | storageBlobDTOs: fileList.value |
| | | .filter(f => f.status === "success") |
| | | .map(f => f.storageBlobDTO), |
| | | batchNo: selectedBatches.map(b => b.id), |
| | | batchNoDetailList: selectedBatches.map(b => ({ |
| | | stockInventoryId: b.id, |
| | | batchNo: b.batchNo, |
| | | quantity: parseFloat(b.deliveryQuantity), |
| | | productModelId: goOutData.value.productModelId, |
| | | })), |
| | | }; |
| | | |
| | | try { |
| | | uni.showLoading({ title: "æäº¤ä¸..." }); |
| | | const res = await addShippingInfo(payload); |
| | | uni.hideLoading(); |
| | | uni.showToast({ title: "åè´§æå" }); |
| | | setTimeout(() => goBack(), 500); |
| | | } catch (e) { |
| | | uni.hideLoading(); |
| | | uni.showToast({ title: "å货失败", icon: "none" }); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | @import "@/static/scss/form-common.scss"; |
| | | |
| | | .approval-process { |
| | | background: #fff; |
| | | margin: 16px; |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | .shipment-page { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 100px; |
| | | } |
| | | |
| | | .approval-header { |
| | | margin-bottom: 16px; |
| | | .form-container { |
| | | padding: 12px 12px 0; |
| | | } |
| | | |
| | | .approval-title { |
| | | font-size: 16px; |
| | | font-weight: 600; |
| | | color: #333; |
| | | display: block; |
| | | margin-bottom: 4px; |
| | | } |
| | | |
| | | .approval-desc { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | /* æ ·å¼å¢å¼ºä¸ºâç®æ´å°åå飿 ¼â */ |
| | | .approval-steps { |
| | | padding-left: 22px; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 11px; |
| | | top: 40px; |
| | | bottom: 40px; |
| | | width: 2px; |
| | | background: linear-gradient( |
| | | to bottom, |
| | | #e6f7ff 0%, |
| | | #bae7ff 50%, |
| | | #91d5ff 100% |
| | | ); |
| | | border-radius: 1px; |
| | | } |
| | | } |
| | | |
| | | .approval-step { |
| | | position: relative; |
| | | margin-bottom: 24px; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: -18px; |
| | | top: 14px; // ä» 8px è°æ´ä¸º 14pxï¼ä¸æåä¸å¿å¯¹é½ |
| | | width: 12px; |
| | | height: 12px; |
| | | background: #fff; |
| | | border: 3px solid #006cfb; |
| | | border-radius: 50%; |
| | | z-index: 2; |
| | | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
| | | } |
| | | } |
| | | |
| | | .step-title { |
| | | top: 12px; |
| | | .form-section { |
| | | margin-bottom: 12px; |
| | | position: relative; |
| | | margin-left: 6px; |
| | | } |
| | | |
| | | .step-title text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | background: #f0f0f0; |
| | | padding: 4px 12px; |
| | | border-radius: 12px; |
| | | position: relative; |
| | | line-height: 1.4; // ç¡®ä¿æåè¡é«ä¸è´ |
| | | overflow: hidden; |
| | | box-shadow: 0 2px 10px rgba(15, 35, 95, 0.05); |
| | | } |
| | | |
| | | .approver-item { |
| | | .section-header-info { |
| | | padding: 10px 18px; |
| | | background: #f8fbff; |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | padding: 16px; |
| | | justify-content: flex-end; |
| | | |
| | | .subtitle { |
| | | font-size: 13px; |
| | | color: #7a8599; |
| | | } |
| | | } |
| | | |
| | | .batch-list { |
| | | padding: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | position: relative; |
| | | border: 1px solid #e6f7ff; |
| | | box-shadow: 0 4px 12px rgba(0, 108, 251, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | width: 48px; |
| | | height: 48px; |
| | | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); |
| | | } |
| | | |
| | | .avatar-text { |
| | | color: #fff; |
| | | font-size: 18px; |
| | | font-weight: 600; |
| | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .approver-info { |
| | | flex: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-name { |
| | | display: block; |
| | | font-size: 16px; |
| | | color: #333; |
| | | font-weight: 500; |
| | | position: relative; |
| | | } |
| | | |
| | | .approver-dept { |
| | | font-size: 12px; |
| | | color: #999; |
| | | background: rgba(0, 108, 251, 0.05); |
| | | padding: 2px 8px; |
| | | border-radius: 8px; |
| | | display: inline-block; |
| | | position: relative; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 2px; |
| | | height: 2px; |
| | | background: #006cfb; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .delete-approver-btn { |
| | | font-size: 16px; |
| | | color: #ff4d4f; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | width: 28px; |
| | | height: 28px; |
| | | border-radius: 50%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: linear-gradient(135deg, #f0f8ff 0%, #e6f7ff 100%); |
| | | border: 2px dashed #006cfb; |
| | | border-radius: 16px; |
| | | padding: 20px; |
| | | color: #006cfb; |
| | | font-size: 14px; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-50%, -50%); |
| | | width: 32px; |
| | | height: 32px; |
| | | border: 2px solid #006cfb; |
| | | border-radius: 50%; |
| | | opacity: 0; |
| | | transition: all 0.3s ease; |
| | | } |
| | | } |
| | | |
| | | .delete-step-btn { |
| | | color: #ff4d4f; |
| | | font-size: 12px; |
| | | background: linear-gradient( |
| | | 135deg, |
| | | rgba(255, 77, 79, 0.1) 0%, |
| | | rgba(255, 77, 79, 0.05) 100% |
| | | ); |
| | | padding: 6px 12px; |
| | | border-radius: 12px; |
| | | display: inline-block; |
| | | position: relative; |
| | | transition: all 0.3s ease; |
| | | |
| | | &::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 6px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 4px; |
| | | height: 4px; |
| | | background: #ff4d4f; |
| | | border-radius: 50%; |
| | | } |
| | | } |
| | | |
| | | .step-line { |
| | | display: none; // éè忥ç线æ¡ï¼ä½¿ç¨ä¼ªå
ç´ ä»£æ¿ |
| | | } |
| | | |
| | | .add-step-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | .footer-btns { |
| | | position: fixed; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | .batch-card { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 0 12px 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); |
| | | border: 1px solid #f0f3f7; |
| | | } |
| | | |
| | | .batch-header { |
| | | padding: 12px 0; |
| | | display: flex; |
| | | justify-content: space-around; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0.75rem 0; |
| | | box-shadow: 0 -0.125rem 0.5rem rgba(0, 0, 0, 0.05); |
| | | z-index: 1000; |
| | | } |
| | | |
| | | .cancel-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 6.375rem; |
| | | background: #c7c9cc; |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | .save-btn { |
| | | font-weight: 400; |
| | | font-size: 1rem; |
| | | color: #ffffff; |
| | | width: 14rem; |
| | | background: linear-gradient(140deg, #00baff 0%, #006cfb 100%); |
| | | box-shadow: 0 0.25rem 0.625rem 0 rgba(3, 88, 185, 0.2); |
| | | border-radius: 2.5rem 2.5rem 2.5rem 2.5rem; |
| | | } |
| | | |
| | | // å¨ç»å®ä¹ |
| | | @keyframes pulse { |
| | | 0% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | .batch-no { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #22324d; |
| | | } |
| | | 50% { |
| | | transform: scale(1.2); |
| | | opacity: 0.7; |
| | | } |
| | | 100% { |
| | | transform: scale(1); |
| | | opacity: 1; |
| | | |
| | | .batch-qty { |
| | | font-size: 13px; |
| | | color: #2979ff; |
| | | background: #eef6ff; |
| | | padding: 2px 8px; |
| | | border-radius: 4px; |
| | | } |
| | | } |
| | | |
| | | @keyframes rotate { |
| | | 0% { |
| | | transform: rotate(0deg); |
| | | } |
| | | 100% { |
| | | transform: rotate(360deg); |
| | | } |
| | | } |
| | | |
| | | @keyframes ripple { |
| | | 0% { |
| | | transform: translate(-50%, -50%) scale(0.8); |
| | | opacity: 1; |
| | | } |
| | | 100% { |
| | | transform: translate(-50%, -50%) scale(1.6); |
| | | opacity: 0; |
| | | } |
| | | } |
| | | |
| | | /* 妿已æ .step-lineï¼è¿éæ´ç²¾åå®ä½å°å·¦ä¾§ä¸å°åç¹å¯¹é½ */ |
| | | .step-line { |
| | | position: absolute; |
| | | left: 4px; |
| | | top: 48px; |
| | | width: 2px; |
| | | height: calc(100% - 48px); |
| | | background: #e5e7eb; |
| | | } |
| | | |
| | | .approver-container { |
| | | display: flex; |
| | | align-items: center; |
| | | background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); |
| | | border-radius: 16px; |
| | | gap: 12px; |
| | | padding: 10px 0; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | } |
| | | |
| | | .approver-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | padding: 8px 10px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | border-radius: 0; |
| | | } |
| | | |
| | | .approver-avatar { |
| | | position: relative; |
| | | width: 40px; |
| | | height: 40px; |
| | | border-radius: 50%; |
| | | background: #f3f4f6; |
| | | border: 2px solid #e5e7eb; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | animation: none; /* ç¦ç¨æè½¬çå¨ç»ï¼åå½ç®æ´ */ |
| | | } |
| | | |
| | | .avatar-text { |
| | | font-size: 14px; |
| | | color: #374151; |
| | | font-weight: 600; |
| | | } |
| | | |
| | | .add-approver-btn { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | background: transparent; |
| | | border: none; |
| | | box-shadow: none; |
| | | padding: 0; |
| | | } |
| | | |
| | | .add-approver-btn .add-circle { |
| | | width: 40px; |
| | | height: 40px; |
| | | border: 2px dashed #a0aec0; |
| | | border-radius: 50%; |
| | | color: #6b7280; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 22px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .add-approver-btn .add-label { |
| | | color: #3b82f6; |
| | | .empty-text { |
| | | padding: 30px 12px; |
| | | text-align: center; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | | |
| | | .upload-container { |
| | | padding: 12px 18px; |
| | | } |
| | | |
| | | :deep(.u-cell-group__title) { |
| | | padding: 14px 18px 10px !important; |
| | | font-size: 15px !important; |
| | | font-weight: 600 !important; |
| | | color: #22324d !important; |
| | | background: #f8fbff !important; |
| | | } |
| | | |
| | | :deep(.u-form-item) { |
| | | padding: 0 18px !important; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <view class="customer-detail-page"> |
| | | <PageHeader title="æ¥ä»·è¯¦æ
" @back="goBack" /> |
| | | |
| | | <PageHeader title="æ¥ä»·è¯¦æ
" |
| | | @back="goBack" /> |
| | | <view class="detail-content"> |
| | | <view class="section"> |
| | | <view class="section-title">åºç¡ä¿¡æ¯</view> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="section"> |
| | | <view class="section-title">审æ¹èç¹</view> |
| | | <view v-if="approverNames.length" class="info-list"> |
| | | <view v-for="(name, index) in approverNames" :key="index" class="info-item"> |
| | | <text class="info-label">审æ¹èç¹ {{ index + 1 }}</text> |
| | | <text class="info-value">{{ name }}</text> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-box"> |
| | | <text>ææ å®¡æ¹èç¹</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="section"> |
| | | <view class="section-title">产åæç»</view> |
| | | <view v-if="detailData.products && detailData.products.length > 0" class="product-list"> |
| | | <view v-for="(item, index) in detailData.products" :key="index" class="product-card"> |
| | | <view v-if="detailData.products && detailData.products.length > 0" |
| | | class="product-list"> |
| | | <view v-for="(item, index) in detailData.products" |
| | | :key="index" |
| | | class="product-card"> |
| | | <view class="product-head">产å {{ index + 1 }}</view> |
| | | <view class="info-item"> |
| | | <text class="info-label">产ååç§°</text> |
| | |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view v-else class="empty-box"> |
| | | <view v-else |
| | | class="empty-box"> |
| | | <text>ææ äº§åæç»</text> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <FooterButtons cancelText="è¿å" confirmText="ç¼è¾" @cancel="goBack" @confirm="goEdit" /> |
| | | <FooterButtons cancelText="è¿å" |
| | | confirmText="ç¼è¾" |
| | | @cancel="goBack" |
| | | @confirm="goEdit" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | const quotationId = ref(""); |
| | | const detailData = ref({}); |
| | | |
| | | const approverNames = computed(() => { |
| | | const approverText = detailData.value.approveUserNames || detailData.value.approverNames || detailData.value.approveUserIds || ""; |
| | | if (Array.isArray(approverText)) return approverText.filter(Boolean); |
| | | return String(approverText) |
| | | .split(",") |
| | | .map(item => item.trim()) |
| | | .filter(Boolean); |
| | | }); |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const goEdit = () => { |
| | | if (!quotationId.value) return; |
| | | uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}` }); |
| | | uni.navigateTo({ |
| | | url: `/pages/sales/salesQuotation/edit?id=${quotationId.value}`, |
| | | }); |
| | | }; |
| | | |
| | | const formatAmount = amount => `Â¥${Number(amount || 0).toFixed(2)}`; |
| | | |
| | | const loadDetailFromStorage = () => { |
| | | const cachedData = uni.getStorageSync("salesQuotationDetail"); |
| | | detailData.value = cachedData || {}; |
| | | if (cachedData && (cachedData.id === quotationId.value || cachedData.id === Number(quotationId.value))) { |
| | | detailData.value = cachedData; |
| | | } else { |
| | | detailData.value = cachedData || {}; |
| | | console.warn("æªæ¾å°å¯¹åºçæ¥ä»·åç¼åæ°æ®"); |
| | | } |
| | | }; |
| | | |
| | | onLoad(options => { |
| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <PageHeader :title="pageTitle" @back="goBack" /> |
| | | |
| | | <PageHeader :title="pageTitle" |
| | | @back="goBack" /> |
| | | <view class="form-container"> |
| | | <up-form |
| | | ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110" |
| | | input-align="right" |
| | | error-message-align="right" |
| | | > |
| | | <u-cell-group title="åºç¡ä¿¡æ¯" class="form-section"> |
| | | <up-form-item label="客æ·åç§°" prop="customer" required> |
| | | <up-input v-model="form.customer" placeholder="è¯·éæ©å®¢æ·" readonly @click="showCustomerSheet = true" /> |
| | | <up-form ref="formRef" |
| | | :model="form" |
| | | :rules="rules" |
| | | label-width="110" |
| | | input-align="right" |
| | | error-message-align="right"> |
| | | <u-cell-group title="åºç¡ä¿¡æ¯" |
| | | class="form-section"> |
| | | <up-form-item label="客æ·åç§°" |
| | | prop="customer" |
| | | required> |
| | | <up-input v-model="form.customer" |
| | | placeholder="è¯·éæ©å®¢æ·" |
| | | readonly |
| | | @click="showCustomerSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="showCustomerSheet = true"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="showCustomerSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="ä¸å¡å" prop="salesperson" required> |
| | | <up-input |
| | | v-model="form.salesperson" |
| | | placeholder="è¯·éæ©ä¸å¡å" |
| | | readonly |
| | | @click="showSalespersonSheet = true" |
| | | /> |
| | | <up-form-item label="ä¸å¡å" |
| | | prop="salesperson" |
| | | required> |
| | | <up-input v-model="form.salesperson" |
| | | placeholder="è¯·éæ©ä¸å¡å" |
| | | readonly |
| | | @click="showSalespersonSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="showSalespersonSheet = true"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="showSalespersonSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æ¥ä»·æ¥æ" prop="quotationDate" required> |
| | | <up-input |
| | | v-model="form.quotationDate" |
| | | placeholder="è¯·éæ©æ¥ä»·æ¥æ" |
| | | readonly |
| | | @click="showQuotationDatePicker = true" |
| | | /> |
| | | <up-form-item label="æ¥ä»·æ¥æ" |
| | | prop="quotationDate" |
| | | required> |
| | | <up-input v-model="form.quotationDate" |
| | | placeholder="è¯·éæ©æ¥ä»·æ¥æ" |
| | | readonly |
| | | @click="showQuotationDatePicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="showQuotationDatePicker = true"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="showQuotationDatePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æææè³" prop="validDate" required> |
| | | <up-input |
| | | v-model="form.validDate" |
| | | placeholder="è¯·éæ©æææ" |
| | | readonly |
| | | @click="showValidDatePicker = true" |
| | | /> |
| | | <up-form-item label="æææè³" |
| | | prop="validDate" |
| | | required> |
| | | <up-input v-model="form.validDate" |
| | | placeholder="è¯·éæ©æææ" |
| | | readonly |
| | | @click="showValidDatePicker = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="showValidDatePicker = true"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="showValidDatePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="仿¬¾æ¹å¼" prop="paymentMethod" required> |
| | | <up-input v-model="form.paymentMethod" placeholder="请è¾å
¥ä»æ¬¾æ¹å¼" clearable /> |
| | | <up-form-item label="仿¬¾æ¹å¼" |
| | | prop="paymentMethod" |
| | | required> |
| | | <up-input v-model="form.paymentMethod" |
| | | placeholder="请è¾å
¥ä»æ¬¾æ¹å¼" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="夿³¨" prop="remark"> |
| | | <up-textarea v-model="form.remark" placeholder="请è¾å
¥å¤æ³¨" auto-height /> |
| | | <up-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <up-textarea v-model="form.remark" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | auto-height /> |
| | | </up-form-item> |
| | | </u-cell-group> |
| | | |
| | | <u-cell-group title="审æ¹èç¹" class="form-section"> |
| | | <u-cell-group title="产åä¿¡æ¯" |
| | | class="form-section"> |
| | | <view class="section-tools"> |
| | | <up-button type="primary" size="small" text="æ°å¢èç¹" @click="addApproverNode" /> |
| | | <up-button type="primary" |
| | | size="small" |
| | | text="æ°å¢äº§å" |
| | | @click="addProduct" /> |
| | | </view> |
| | | <view v-if="salespersonList.length === 0" class="empty-text"> |
| | | <text>ææ å¯é审æ¹äººï¼è¯·æ£æ¥ç¨æ·æ°æ®</text> |
| | | </view> |
| | | <view class="node-list"> |
| | | <view v-for="(node, index) in approverNodes" :key="node.id" class="node-card"> |
| | | <view class="node-top"> |
| | | <text class="node-title">审æ¹èç¹ {{ index + 1 }}</text> |
| | | <up-icon |
| | | v-if="approverNodes.length > 1" |
| | | name="trash" |
| | | color="#ee0a24" |
| | | size="18" |
| | | @click="removeApproverNode(index)" |
| | | ></up-icon> |
| | | </view> |
| | | <view class="picker-field" @click="openApproverPicker(index)"> |
| | | <up-input :model-value="node.nickName || ''" placeholder="è¯·éæ©å®¡æ¹äºº" readonly disabled /> |
| | | <up-icon name="arrow-right" color="#909399" size="16"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-cell-group> |
| | | |
| | | <u-cell-group title="产åä¿¡æ¯" class="form-section"> |
| | | <view class="section-tools"> |
| | | <up-button type="primary" size="small" text="æ°å¢äº§å" @click="addProduct" /> |
| | | </view> |
| | | <view v-if="form.products.length === 0" class="empty-text"> |
| | | <view v-if="form.products.length === 0" |
| | | class="empty-text"> |
| | | <text>ææ äº§åï¼è¯·å
æ·»å 产å</text> |
| | | </view> |
| | | <view v-else class="product-list"> |
| | | <view v-for="(product, index) in form.products" :key="product.uid" class="product-card"> |
| | | <view v-else |
| | | class="product-list"> |
| | | <view v-for="(product, index) in form.products" |
| | | :key="product.uid" |
| | | class="product-card"> |
| | | <view class="product-header"> |
| | | <text class="product-title">产å {{ index + 1 }}</text> |
| | | <up-icon name="trash" color="#ee0a24" size="18" @click="removeProduct(index)"></up-icon> |
| | | <up-icon name="trash" |
| | | color="#ee0a24" |
| | | size="18" |
| | | @click="removeProduct(index)"></up-icon> |
| | | </view> |
| | | <up-divider></up-divider> |
| | | <view class="product-body"> |
| | | <up-form-item label="产ååç§°"> |
| | | <up-input |
| | | v-model="product.product" |
| | | placeholder="è¯·éæ©äº§å" |
| | | readonly |
| | | @click="openProductPicker(index)" |
| | | /> |
| | | <up-input v-model="product.product" |
| | | placeholder="è¯·éæ©äº§å" |
| | | readonly |
| | | @click="openProductPicker(index)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="openProductPicker(index)"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="openProductPicker(index)"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="è§æ ¼åå·"> |
| | | <up-input |
| | | v-model="product.specification" |
| | | placeholder="è¯·éæ©è§æ ¼åå·" |
| | | readonly |
| | | @click="openModelPicker(index)" |
| | | /> |
| | | <up-input v-model="product.ProductModel" |
| | | placeholder="è¯·éæ©è§æ ¼åå·" |
| | | readonly |
| | | @click="openModelPicker(index)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" @click="openModelPicker(index)"></up-icon> |
| | | <up-icon name="arrow-right" |
| | | @click="openModelPicker(index)"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="åä½"> |
| | | <up-input v-model="product.unit" placeholder="请è¾å
¥åä½" clearable /> |
| | | <up-input v-model="product.unit" |
| | | placeholder="请è¾å
¥åä½" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="æ°é"> |
| | | <up-input |
| | | v-model="product.quantity" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ°é" |
| | | clearable |
| | | @blur="calculateAmount(product)" |
| | | /> |
| | | <up-input v-model="product.quantity" |
| | | type="number" |
| | | placeholder="请è¾å
¥æ°é" |
| | | clearable |
| | | @blur="calculateAmount(product)" /> |
| | | </up-form-item> |
| | | <up-form-item label="åä»·"> |
| | | <up-input |
| | | v-model="product.unitPrice" |
| | | type="number" |
| | | placeholder="请è¾å
¥åä»·" |
| | | clearable |
| | | @blur="calculateAmount(product)" |
| | | /> |
| | | <up-input v-model="product.unitPrice" |
| | | type="number" |
| | | placeholder="请è¾å
¥åä»·" |
| | | clearable |
| | | @blur="calculateAmount(product)" /> |
| | | </up-form-item> |
| | | <up-form-item label="éé¢"> |
| | | <up-input :model-value="formatAmount(product.amount)" disabled placeholder="èªå¨è®¡ç®" /> |
| | | <up-input :model-value="formatAmount(product.amount)" |
| | | disabled |
| | | placeholder="èªå¨è®¡ç®" /> |
| | | </up-form-item> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </u-cell-group> |
| | | |
| | | <u-cell-group title="æ±æ»ä¿¡æ¯" class="form-section"> |
| | | <u-cell-group title="æ±æ»ä¿¡æ¯" |
| | | class="form-section"> |
| | | <up-form-item label="æ¥ä»·æ»é¢"> |
| | | <up-input :model-value="formatAmount(totalAmount)" disabled placeholder="èªå¨æ±æ»" /> |
| | | <up-input :model-value="formatAmount(totalAmount)" |
| | | disabled |
| | | placeholder="èªå¨æ±æ»" /> |
| | | </up-form-item> |
| | | </u-cell-group> |
| | | </up-form> |
| | | </view> |
| | | |
| | | <FooterButtons :loading="loading" confirmText="ä¿å" @cancel="goBack" @confirm="handleSubmit" /> |
| | | |
| | | <up-action-sheet :show="showCustomerSheet" title="鿩客æ·" :actions="customerActions" @select="onSelectCustomer" @close="showCustomerSheet = false" /> |
| | | <up-action-sheet :show="showSalespersonSheet" title="éæ©ä¸å¡å" :actions="salespersonActions" @select="onSelectSalesperson" @close="showSalespersonSheet = false" /> |
| | | <up-action-sheet :show="showProductSheet" title="éæ©äº§å" :actions="productActions" @select="onSelectProduct" @close="showProductSheet = false" /> |
| | | <up-action-sheet :show="showModelSheet" title="éæ©è§æ ¼åå·" :actions="modelActions" @select="onSelectModel" @close="showModelSheet = false" /> |
| | | <up-datetime-picker :show="showQuotationDatePicker" v-model="quotationDateValue" mode="date" @confirm="onQuotationDateConfirm" @cancel="showQuotationDatePicker = false" /> |
| | | <up-datetime-picker :show="showValidDatePicker" v-model="validDateValue" mode="date" @confirm="onValidDateConfirm" @cancel="showValidDatePicker = false" /> |
| | | <FooterButtons :loading="loading" |
| | | confirmText="ä¿å" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" /> |
| | | <up-action-sheet :show="showCustomerSheet" |
| | | title="鿩客æ·" |
| | | :actions="customerActions" |
| | | @select="onSelectCustomer" |
| | | @close="showCustomerSheet = false" /> |
| | | <up-action-sheet :show="showSalespersonSheet" |
| | | title="éæ©ä¸å¡å" |
| | | :actions="salespersonActions" |
| | | @select="onSelectSalesperson" |
| | | @close="showSalespersonSheet = false" /> |
| | | <up-action-sheet :show="showProductSheet" |
| | | title="éæ©äº§å" |
| | | :actions="productActions" |
| | | @select="onSelectProduct" |
| | | @close="showProductSheet = false" /> |
| | | <up-action-sheet :show="showModelSheet" |
| | | title="éæ©è§æ ¼åå·" |
| | | :actions="modelActions" |
| | | @select="onSelectModel" |
| | | @close="showModelSheet = false" /> |
| | | <up-datetime-picker :show="showQuotationDatePicker" |
| | | v-model="quotationDateValue" |
| | | mode="date" |
| | | @confirm="onQuotationDateConfirm" |
| | | @cancel="showQuotationDatePicker = false" /> |
| | | <up-datetime-picker :show="showValidDatePicker" |
| | | v-model="validDateValue" |
| | | mode="date" |
| | | @confirm="onValidDateConfirm" |
| | | @cancel="showValidDatePicker = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | import { addQuotation, getCustomerList, getQuotationDetail, updateQuotation } from "@/api/salesManagement/salesQuotation"; |
| | | import { |
| | | addQuotation, |
| | | getCustomerList, |
| | | updateQuotation, |
| | | } from "@/api/salesManagement/salesQuotation"; |
| | | |
| | | const formRef = ref(); |
| | | const loading = ref(false); |
| | |
| | | const modelActions = ref([]); |
| | | |
| | | let uidSeed = 1; |
| | | let nextApproverId = 2; |
| | | |
| | | const form = ref({ |
| | | id: undefined, |
| | | quotationNo: "", |
| | | customerId: undefined, |
| | | customer: "", |
| | | salesperson: "", |
| | | quotationDate: "", |
| | | validDate: "", |
| | | paymentMethod: "", |
| | | status: "å¾
审æ¹", |
| | | status: "è稿", |
| | | remark: "", |
| | | approveUserIds: "", |
| | | products: [], |
| | | subtotal: 0, |
| | | freight: 0, |
| | | otherFee: 0, |
| | | discountRate: 0, |
| | | discountAmount: 0, |
| | | totalAmount: 0, |
| | | }); |
| | | |
| | | const approverNodes = ref([{ id: 1, userId: "", nickName: "" }]); |
| | | |
| | | const rules = { |
| | | customer: [{ required: true, message: "è¯·éæ©å®¢æ·", trigger: "change" }], |
| | | salesperson: [{ required: true, message: "è¯·éæ©ä¸å¡å", trigger: "change" }], |
| | | quotationDate: [{ required: true, message: "è¯·éæ©æ¥ä»·æ¥æ", trigger: "change" }], |
| | | quotationDate: [ |
| | | { required: true, message: "è¯·éæ©æ¥ä»·æ¥æ", trigger: "change" }, |
| | | ], |
| | | validDate: [{ required: true, message: "è¯·éæ©æææ", trigger: "change" }], |
| | | paymentMethod: [{ required: true, message: "请è¾å
¥ä»æ¬¾æ¹å¼", trigger: "blur" }], |
| | | paymentMethod: [ |
| | | { required: true, message: "请è¾å
¥ä»æ¬¾æ¹å¼", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | const pageTitle = computed(() => (quotationId.value ? "ç¼è¾æ¥ä»·" : "æ°å¢æ¥ä»·")); |
| | | const totalAmount = computed(() => |
| | | Number((form.value.products || []).reduce((sum, item) => sum + Number(item.amount || 0), 0).toFixed(2)) |
| | | Number( |
| | | (form.value.products || []) |
| | | .reduce((sum, item) => sum + Number(item.amount || 0), 0) |
| | | .toFixed(2) |
| | | ) |
| | | ); |
| | | const customerActions = computed(() => customerList.value.map(item => ({ name: item.customerName, value: item.customerName }))); |
| | | const salespersonActions = computed(() => salespersonList.value.map(item => ({ name: item.nickName, value: item.nickName }))); |
| | | const productActions = computed(() => productList.value.map(item => ({ name: item.label, value: item.value, label: item.label }))); |
| | | const customerActions = computed(() => |
| | | customerList.value.map(item => ({ |
| | | name: item.customerName, |
| | | value: item.id, |
| | | })) |
| | | ); |
| | | const salespersonActions = computed(() => |
| | | salespersonList.value.map(item => ({ |
| | | name: item.nickName, |
| | | value: item.nickName, |
| | | })) |
| | | ); |
| | | const productActions = computed(() => |
| | | productList.value.map(item => ({ |
| | | name: item.label, |
| | | value: item.value, |
| | | label: item.label, |
| | | })) |
| | | ); |
| | | |
| | | const createEmptyProduct = () => ({ |
| | | uid: `p_${uidSeed++}`, |
| | | productId: "", |
| | | product: "", |
| | | specificationId: "", |
| | | specification: "", |
| | | productModelId: "", |
| | | ProductModel: "", |
| | | unit: "", |
| | | quantity: 1, |
| | | unitPrice: 0, |
| | |
| | | if (item.children && item.children.length) { |
| | | walk(item.children); |
| | | } else { |
| | | result.push({ label: item.label || item.productName || "", value: item.id || item.value }); |
| | | result.push({ |
| | | label: item.label || item.productName || "", |
| | | value: item.id || item.value, |
| | | }); |
| | | } |
| | | }); |
| | | }; |
| | |
| | | const goBack = () => uni.navigateBack(); |
| | | |
| | | const calculateAmount = product => { |
| | | product.amount = Number((Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2)); |
| | | product.amount = Number( |
| | | (Number(product.quantity || 0) * Number(product.unitPrice || 0)).toFixed(2) |
| | | ); |
| | | form.value.totalAmount = totalAmount.value; |
| | | }; |
| | | |
| | | const addApproverNode = () => approverNodes.value.push({ id: nextApproverId++, userId: "", nickName: "" }); |
| | | const removeApproverNode = index => approverNodes.value.splice(index, 1); |
| | | const openApproverPicker = index => { |
| | | uni.setStorageSync("stepIndex", index); |
| | | uni.navigateTo({ |
| | | url: "/pages/cooperativeOffice/collaborativeApproval/contactSelect", |
| | | }); |
| | | }; |
| | | const addProduct = () => form.value.products.push(createEmptyProduct()); |
| | | const removeProduct = index => { |
| | | form.value.products.splice(index, 1); |
| | |
| | | }; |
| | | |
| | | const fetchModelOptions = async (productId, product) => { |
| | | const rows = await modelList({ id: productId }).catch(() => []); |
| | | product.modelOptions = Array.isArray(rows) ? rows : []; |
| | | try { |
| | | const res = await modelList({ id: productId }); |
| | | const rows = res?.data?.records || res?.data || res?.records || res || []; |
| | | product.modelOptions = Array.isArray(rows) ? rows : []; |
| | | } catch (error) { |
| | | console.error("è·åè§æ ¼åå·å¤±è´¥:", error); |
| | | product.modelOptions = []; |
| | | } |
| | | }; |
| | | |
| | | const openProductPicker = index => { |
| | |
| | | uni.showToast({ title: "请å
éæ©äº§å", icon: "none" }); |
| | | return; |
| | | } |
| | | modelActions.value = (current.modelOptions || []).map(item => ({ name: item.model, value: item.id, unit: item.unit })); |
| | | modelActions.value = (current.modelOptions || []).map(item => ({ |
| | | name: item.model || item.specification, |
| | | value: item.id, |
| | | unit: item.unit, |
| | | })); |
| | | if (!modelActions.value.length) { |
| | | uni.showToast({ title: "ææ è§æ ¼åå·", icon: "none" }); |
| | | return; |
| | |
| | | }; |
| | | |
| | | const onSelectCustomer = action => { |
| | | form.value.customer = action.value; |
| | | form.value.customerId = action.value; |
| | | form.value.customer = action.name; |
| | | showCustomerSheet.value = false; |
| | | }; |
| | | const onSelectSalesperson = action => { |
| | | form.value.salesperson = action.value; |
| | | showSalespersonSheet.value = false; |
| | | }; |
| | | const onSelectApprover = data => { |
| | | const { stepIndex, contact } = data || {}; |
| | | if (stepIndex === undefined || !contact) return; |
| | | if (!approverNodes.value[stepIndex]) return; |
| | | approverNodes.value[stepIndex].userId = contact.userId; |
| | | approverNodes.value[stepIndex].nickName = contact.nickName; |
| | | }; |
| | | const onSelectProduct = action => { |
| | | const current = form.value.products[currentProductIndex.value]; |
| | | if (!current) return; |
| | | current.productId = action.value; |
| | | current.product = action.label; |
| | | current.specificationId = ""; |
| | | current.specification = ""; |
| | | current.productModelId = ""; |
| | | current.ProductModel = ""; |
| | | current.unit = ""; |
| | | current.modelOptions = []; |
| | | showProductSheet.value = false; |
| | |
| | | const onSelectModel = action => { |
| | | const current = form.value.products[currentProductIndex.value]; |
| | | if (!current) return; |
| | | current.specificationId = action.value; |
| | | current.specification = action.name; |
| | | current.productModelId = action.value; |
| | | current.ProductModel = action.name; |
| | | current.unit = action.unit || current.unit; |
| | | showModelSheet.value = false; |
| | | }; |
| | |
| | | }; |
| | | |
| | | const fetchBaseOptions = async () => { |
| | | const [customers, users, productTree] = await Promise.all([ |
| | | getCustomerList({ current: -1, size: -1 }).catch(() => ({})), |
| | | userListNoPageByTenantId().catch(() => ({})), |
| | | productTreeList().catch(() => []), |
| | | ]); |
| | | const [customers, users, productTree] = await Promise.all([ |
| | | getCustomerList({ current: -1, size: -1 }).catch(() => ({})), |
| | | userListNoPageByTenantId().catch(() => ({})), |
| | | productTreeList().catch(() => []), |
| | | ]); |
| | | customerList.value = customers?.data?.records || customers?.records || []; |
| | | const userRows = users?.data || []; |
| | | salespersonList.value = Array.isArray(userRows) ? userRows : []; |
| | | productList.value = flattenProductTree(Array.isArray(productTree) ? productTree : productTree?.data || []); |
| | | productList.value = flattenProductTree( |
| | | Array.isArray(productTree) ? productTree : productTree?.data || [] |
| | | ); |
| | | }; |
| | | |
| | | // æ ¹æ®åç§°åæ¥èç¹ idï¼ä¾¿äºä»
ååç§°æ¶çåæ¾ |
| | | const findNodeIdByLabel = (nodes, label) => { |
| | | if (!label) return null; |
| | | for (let i = 0; i < nodes.length; i++) { |
| | | const node = nodes[i]; |
| | | if (node.label === label) return node.value; |
| | | if (node.children && node.children.length > 0) { |
| | | const found = findNodeIdByLabel(node.children, label); |
| | | if (found !== null && found !== undefined) return found; |
| | | } |
| | | } |
| | | return null; |
| | | }; |
| | | |
| | | const normalizeProductRows = async rows => { |
| | | const normalized = await Promise.all((Array.isArray(rows) ? rows : []).map(async item => { |
| | | const row = { |
| | | uid: `p_${uidSeed++}`, |
| | | productId: item.productId || "", |
| | | product: item.product || item.productName || "", |
| | | specificationId: item.specificationId || "", |
| | | specification: item.specification || "", |
| | | unit: item.unit || "", |
| | | quantity: Number(item.quantity || 1), |
| | | unitPrice: Number(item.unitPrice || 0), |
| | | amount: Number(item.amount || 0), |
| | | modelOptions: [], |
| | | }; |
| | | if (row.productId) await fetchModelOptions(row.productId, row); |
| | | return row; |
| | | })); |
| | | const normalized = await Promise.all( |
| | | (Array.isArray(rows) ? rows : []).map(async item => { |
| | | const productName = item.product || item.productName || ""; |
| | | // ä¼å
ç¨ productIdï¼å¦æåªæåç§°ï¼å°è¯åæ¥ id ä»¥ä¾¿éæ©å¨åæ¾ |
| | | let resolvedProductId = |
| | | item.productId || |
| | | findNodeIdByLabel(productList.value, productName) || |
| | | ""; |
| | | |
| | | const row = { |
| | | uid: `p_${uidSeed++}`, |
| | | productId: resolvedProductId, |
| | | product: productName, |
| | | productModelId: item.productModelId || "", |
| | | ProductModel: item.ProductModel || item.specification || "", |
| | | unit: item.unit || "", |
| | | quantity: Number(item.quantity || 1), |
| | | unitPrice: Number(item.unitPrice || 0), |
| | | amount: Number(item.amount || 0), |
| | | modelOptions: [], |
| | | }; |
| | | |
| | | if (row.productId) { |
| | | await fetchModelOptions(row.productId, row); |
| | | // å¦ææ²¡æ productModelId 使 ProductModel åç§°ï¼å°è¯ä» modelOptions ä¸å¹é
ID |
| | | if (!row.productModelId && row.ProductModel) { |
| | | const foundModel = row.modelOptions.find( |
| | | m => |
| | | m.model === row.ProductModel || |
| | | m.specification === row.ProductModel |
| | | ); |
| | | if (foundModel) { |
| | | row.productModelId = foundModel.id; |
| | | // ç»ä¸ä½¿ç¨ modelOptions ä¸çåæ®µ |
| | | row.ProductModel = |
| | | foundModel.model || foundModel.specification || row.ProductModel; |
| | | row.unit = foundModel.unit || row.unit; |
| | | } |
| | | } |
| | | } |
| | | return row; |
| | | }) |
| | | ); |
| | | form.value.products = normalized; |
| | | }; |
| | | |
| | | const loadDetail = async () => { |
| | | if (!quotationId.value) return; |
| | | uni.showLoading({ title: "å è½½ä¸...", mask: true }); |
| | | try { |
| | | const res = await getQuotationDetail({ id: quotationId.value }); |
| | | const data = res?.data || {}; |
| | | |
| | | // ç´æ¥ä»æ¬å°åå¨è·åæ°æ®ï¼ä¸åè°ç¨è¯¦æ
æ¥å£ |
| | | const cachedData = uni.getStorageSync("salesQuotationDetail"); |
| | | if ( |
| | | cachedData && |
| | | (cachedData.id === quotationId.value || |
| | | cachedData.id === Number(quotationId.value)) |
| | | ) { |
| | | const data = cachedData; |
| | | form.value = { |
| | | ...form.value, |
| | | id: data.id, |
| | | quotationNo: data.quotationNo || "", |
| | | customerId: data.customerId, |
| | | customer: data.customer || "", |
| | | salesperson: data.salesperson || "", |
| | | quotationDate: data.quotationDate || "", |
| | | validDate: data.validDate || "", |
| | | paymentMethod: data.paymentMethod || "", |
| | | status: data.status || "å¾
审æ¹", |
| | | status: data.status || "è稿", |
| | | remark: data.remark || "", |
| | | subtotal: data.subtotal || 0, |
| | | freight: data.freight || 0, |
| | | otherFee: data.otherFee || 0, |
| | | discountRate: data.discountRate || 0, |
| | | discountAmount: data.discountAmount || 0, |
| | | totalAmount: data.totalAmount || 0, |
| | | }; |
| | | await normalizeProductRows(data.products || []); |
| | | if (data.approveUserIds) { |
| | | const ids = String(data.approveUserIds).split(",").map(item => item.trim()).filter(Boolean); |
| | | approverNodes.value = ids.map((userId, index) => ({ |
| | | id: index + 1, |
| | | userId, |
| | | nickName: salespersonList.value.find(item => String(item.userId) === String(userId))?.nickName || "", |
| | | })); |
| | | nextApproverId = approverNodes.value.length + 1; |
| | | } |
| | | form.value.totalAmount = totalAmount.value; |
| | | } catch { |
| | | uni.showToast({ title: "è·å详æ
失败", icon: "error" }); |
| | | } finally { |
| | | uni.hideLoading(); |
| | | } else { |
| | | console.warn("æªæ¾å°ç¼åçæ¥ä»·å详æ
æ°æ®"); |
| | | } |
| | | }; |
| | | |
| | |
| | | uni.showToast({ title: "请è³å°æ·»å ä¸ä¸ªäº§å", icon: "none" }); |
| | | return false; |
| | | } |
| | | const invalid = form.value.products.some(item => !item.productId || !item.specificationId || !item.unit || !Number(item.quantity) || !Number(item.unitPrice)); |
| | | const invalid = form.value.products.some( |
| | | item => |
| | | !item.productId || |
| | | !item.productModelId || |
| | | !item.unit || |
| | | !Number(item.quantity) || |
| | | !Number(item.unitPrice) |
| | | ); |
| | | if (invalid) { |
| | | uni.showToast({ title: "请å®å产åä¿¡æ¯", icon: "none" }); |
| | | return false; |
| | | } |
| | | return true; |
| | | }; |
| | | const validateApprovers = () => { |
| | | if (approverNodes.value.some(item => !item.userId)) { |
| | | uni.showToast({ title: "è¯·éæ©å®¡æ¹äºº", icon: "none" }); |
| | | return false; |
| | | } |
| | | return true; |
| | |
| | | |
| | | const handleSubmit = async () => { |
| | | const valid = await formRef.value.validate().catch(() => false); |
| | | if (!valid || !validateApprovers() || !validateProducts()) return; |
| | | if (!valid || !validateProducts()) return; |
| | | loading.value = true; |
| | | |
| | | // åæ¥ææ°çæ»é¢ |
| | | form.value.totalAmount = totalAmount.value; |
| | | form.value.subtotal = totalAmount.value; |
| | | |
| | | const payload = { |
| | | ...form.value, |
| | | approveUserIds: approverNodes.value.map(item => item.userId).join(","), |
| | | totalAmount: totalAmount.value, |
| | | products: form.value.products.map(item => ({ |
| | | productId: item.productId, |
| | | product: item.product, |
| | | specificationId: item.specificationId, |
| | | specification: item.specification, |
| | | productModelId: item.productModelId, |
| | | ProductModel: item.ProductModel, |
| | | quantity: Number(item.quantity || 0), |
| | | unit: item.unit, |
| | | unitPrice: Number(item.unitPrice || 0), |
| | |
| | | |
| | | onMounted(async () => { |
| | | await fetchBaseOptions(); |
| | | uni.$on("selectContact", onSelectApprover); |
| | | if (quotationId.value) { |
| | | await loadDetail(); |
| | | } |
| | | }); |
| | | |
| | | onUnmounted(() => { |
| | | uni.$off("selectContact", onSelectApprover); |
| | | uni.removeStorageSync("stepIndex"); |
| | | }); |
| | | onUnmounted(() => {}); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | padding: 12px 12px 0; |
| | | } |
| | | |
| | | .node-list, |
| | | .product-list { |
| | | padding: 12px; |
| | | display: flex; |
| | |
| | | gap: 12px; |
| | | } |
| | | |
| | | .node-card { |
| | | background: #f8fbff; |
| | | border-radius: 12px; |
| | | padding: 12px; |
| | | border: 1px solid #e6eef8; |
| | | } |
| | | |
| | | .picker-field { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .picker-field :deep(.u-input) { |
| | | flex: 1; |
| | | } |
| | | |
| | | .node-top, |
| | | .product-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | } |
| | | |
| | | .node-title, |
| | | .product-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | |
| | | <template> |
| | | <view class="sales-account"> |
| | | <PageHeader title="é宿¥ä»·" @back="goBack" /> |
| | | |
| | | <PageHeader title="é宿¥ä»·" |
| | | @back="goBack" /> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | v-model="quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·æç´¢" |
| | | clearable |
| | | @change="getList" |
| | | /> |
| | | <up-input class="search-text" |
| | | v-model="quotationNo" |
| | | placeholder="请è¾å
¥æ¥ä»·åå·æç´¢" |
| | | clearable |
| | | @change="getList" /> |
| | | </view> |
| | | <view class="filter-button" @click="getList"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | <view class="filter-button" |
| | | @click="getList"> |
| | | <up-icon name="search" |
| | | size="24" |
| | | color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="tabs-section"> |
| | | <up-tabs |
| | | v-model="tabValue" |
| | | :list="tabList" |
| | | itemStyle="width: 20%;height: 80rpx;" |
| | | @change="onTabChange" |
| | | /> |
| | | <up-tabs v-model="tabValue" |
| | | :list="tabList" |
| | | itemStyle="width: 20%;height: 80rpx;" |
| | | @change="onTabChange" /> |
| | | </view> |
| | | |
| | | <view v-if="quotationList.length > 0" class="ledger-list"> |
| | | <view v-for="item in quotationList" :key="item.id" class="ledger-item"> |
| | | <view v-if="quotationList.length > 0" |
| | | class="ledger-list"> |
| | | <view v-for="item in quotationList" |
| | | :key="item.id" |
| | | class="ledger-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="document-icon"> |
| | | <up-icon name="file-text" size="16" color="#ffffff"></up-icon> |
| | | <up-icon name="file-text" |
| | | size="16" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | <text class="item-id">{{ item.quotationNo || "-" }}</text> |
| | | </view> |
| | | <text class="item-index">{{ item.status || "-" }}</text> |
| | | <up-tag :text="item.status || 'å¾
审æ¹'" |
| | | :type="getStatusType(item.status)" |
| | | size="mini" |
| | | shape="circle" /> |
| | | </view> |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">客æ·åç§°</text> |
| | |
| | | <text class="detail-value">{{ item.validDate || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">仿¬¾æ¹å¼</text> |
| | | <text class="detail-value">{{ item.paymentMethod || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">æ¥ä»·éé¢</text> |
| | | <text class="detail-value highlight">{{ formatAmount(item.totalAmount) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <view class="detail-row" |
| | | v-if="item.remark"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || "-" }}</text> |
| | | <text class="detail-value">{{ item.remark }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="action-buttons"> |
| | | <up-button |
| | | class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | :disabled="!canEdit(item)" |
| | | @click="goEdit(item)" |
| | | > |
| | | ç¼è¾ |
| | | </up-button> |
| | | <up-button class="action-btn" size="small" @click="goDetail(item)">详æ
</up-button> |
| | | <up-button class="action-btn" size="small" type="error" plain @click="handleDelete(item)"> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | :disabled="!canEdit(item)" |
| | | @click="goEdit(item)"> |
| | | ç¼è¾ |
| | | </up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | @click="goDetail(item)">详æ
</up-button> |
| | | <up-button class="action-btn" |
| | | size="small" |
| | | type="error" |
| | | plain |
| | | @click="handleDelete(item)"> |
| | | å é¤ |
| | | </up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ é宿¥ä»·æ°æ®</text> |
| | | </view> |
| | | |
| | | <view class="fab-button" @click="goAdd"> |
| | | <up-icon name="plus" size="28" color="#ffffff"></up-icon> |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | | <up-icon name="plus" |
| | | size="28" |
| | | color="#ffffff"></up-icon> |
| | | </view> |
| | | </view> |
| | | </template> |
| | |
| | | import { reactive, ref } from "vue"; |
| | | import { onShow } from "@dcloudio/uni-app"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import { deleteQuotation, getQuotationList } from "@/api/salesManagement/salesQuotation"; |
| | | import { |
| | | deleteQuotation, |
| | | getQuotationList, |
| | | } from "@/api/salesManagement/salesQuotation"; |
| | | |
| | | const quotationNo = ref(""); |
| | | const quotationList = ref([]); |
| | |
| | | }; |
| | | |
| | | const goAdd = () => { |
| | | uni.removeStorageSync("salesQuotationDetail"); |
| | | uni.navigateTo({ url: "/pages/sales/salesQuotation/edit" }); |
| | | }; |
| | | |
| | | const goEdit = item => { |
| | | if (!canEdit(item)) return; |
| | | uni.setStorageSync("salesQuotationDetail", item || {}); |
| | | uni.navigateTo({ url: `/pages/sales/salesQuotation/edit?id=${item.id}` }); |
| | | }; |
| | | |
| | |
| | | return `Â¥${num.toFixed(2)}`; |
| | | }; |
| | | |
| | | const getStatusType = status => { |
| | | const statusMap = { |
| | | å¾
审æ¹: "info", |
| | | å®¡æ ¸ä¸: "primary", |
| | | éè¿: "success", |
| | | æç»: "danger", |
| | | }; |
| | | return statusMap[status] || "info"; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | uni.showLoading({ title: "å è½½ä¸...", mask: true }); |
| | | getQuotationList({ |