| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view class="stock-out-page"> |
| | | <!-- 页颿 é¢ --> |
| | | <PageHeader title="èªå®ä¹åºåº" @back="goBack" /> |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <u-input |
| | | v-model="searchForm.supplierName" |
| | | placeholder="请è¾å
¥ä¾åºååç§°" |
| | | border="none" |
| | | clearable |
| | | /> |
| | | </view> |
| | | <view class="search-button" @click="handleQuery"> |
| | | <u-icon name="search" size="24" color="#999"></u-icon> |
| | | </view> |
| | | </view> |
| | | <view class="date-filter" @click="openDatePickerHandler"> |
| | | <text class="date-text">{{ searchForm.timeStr || 'éæ©æ¥æ' }}</text> |
| | | <up-icon name="calendar" size="18" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- æ¥æéæ©å¨ --> |
| | | <up-popup :show="showDatePicker" mode="bottom" @close="showDatePicker = false"> |
| | | <up-datetime-picker |
| | | :show="true" |
| | | v-model="dateValue" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showDatePicker = false" |
| | | mode="date" |
| | | /> |
| | | </up-popup> |
| | | |
| | | <!-- æ°æ®å表 --> |
| | | <view class="stock-list" v-if="tableData.length > 0"> |
| | | <view v-for="(item, index) in tableData" :key="item.id" class="stock-item"> |
| | | <view class="item-header"> |
| | | <view class="item-left"> |
| | | <view class="batch-icon"> |
| | | <u-icon name="file-text" size="16" color="#ffffff"></u-icon> |
| | | </view> |
| | | <text class="batch-text">{{ item.inboundBatches || 'æªç¥æ¹æ¬¡' }}</text> |
| | | </view> |
| | | <view class="item-right"> |
| | | <text class="time-text">{{ item.inboundDate || item.createTime || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | <u-divider></u-divider> |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ä¾åºååç§°</text> |
| | | <text class="detail-value">{{ item.supplierName || 'æªç¥ä¾åºå' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产å大类</text> |
| | | <text class="detail-value">{{ item.productCategory || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è§æ ¼åå·</text> |
| | | <text class="detail-value">{{ item.specificationModel || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å
¥åºæ°é</text> |
| | | <text class="detail-value highlight">{{ item.inboundNum || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å«ç¨åä»·</text> |
| | | <text class="detail-value">Â¥{{ item.taxInclusiveUnitPrice || 0 }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å«ç¨æ»ä»·</text> |
| | | <text class="detail-value price">Â¥{{ item.taxInclusiveTotalPrice || 0 }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="item-actions"> |
| | | <u-button type="primary" size="small" @click="openForm(item)">é¢ç¨</u-button> |
| | | <u-button type="warning" size="small" plain @click="handleOut">导åº</u-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <view v-else class="no-data"> |
| | | <text>ææ æ°æ®</text> |
| | | </view> |
| | | |
| | | <!-- å è½½æ´å¤ --> |
| | | <view class="load-more" v-if="tableData.length > 0"> |
| | | <u-loadmore :status="loadStatus" @loadmore="loadMore" /> |
| | | </view> |
| | | |
| | | <!-- åºåºè¡¨åå¼¹çª --> |
| | | <u-popup |
| | | v-model="dialogFormVisible" |
| | | mode="center" |
| | | :closeable="true" |
| | | @close="closeDia" |
| | | round="10" |
| | | > |
| | | <view class="popup-content"> |
| | | <view class="popup-header"> |
| | | <text class="popup-title">æ°å¢åºåº</text> |
| | | </view> |
| | | |
| | | <view class="popup-body"> |
| | | <u-form :model="form" :rules="rules" ref="formRef" labelWidth="80"> |
| | | <u-form-item label="åºåºæ°é" prop="inboundQuantity" borderBottom> |
| | | <u-input |
| | | v-model="form.inboundQuantity" |
| | | placeholder="请è¾å
¥åºåºæ°é" |
| | | type="number" |
| | | border="none" |
| | | /> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="åºåºæ¥æ" prop="inboundTime" borderBottom> |
| | | <u-input |
| | | v-model="form.inboundTime" |
| | | placeholder="è¯·éæ©åºåºæ¥æ" |
| | | border="none" |
| | | readonly |
| | | @click="showOutDatePicker = true" |
| | | > |
| | | <template #suffix> |
| | | <u-icon name="calendar" size="18"></u-icon> |
| | | </template> |
| | | </u-input> |
| | | </u-form-item> |
| | | |
| | | <u-form-item label="åºåºäºº" prop="nickName" borderBottom> |
| | | <u-select |
| | | v-model="form.nickName" |
| | | :list="userList" |
| | | labelName="nickName" |
| | | valueName="userId" |
| | | placeholder="è¯·éæ©åºåºäºº" |
| | | ></u-select> |
| | | </u-form-item> |
| | | </u-form> |
| | | </view> |
| | | |
| | | <view class="popup-footer"> |
| | | <u-button type="primary" @click="submitForm" size="normal">确认</u-button> |
| | | <u-button @click="closeDia" size="normal" plain>åæ¶</u-button> |
| | | </view> |
| | | </view> |
| | | </u-popup> |
| | | |
| | | <!-- åºåºæ¥æéæ©å¨ --> |
| | | <u-datetime-picker |
| | | v-model="form.inboundTime" |
| | | :show="showOutDatePicker" |
| | | mode="date" |
| | | @confirm="showOutDatePicker = false" |
| | | @cancel="showOutDatePicker = false" |
| | | /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, onMounted, getCurrentInstance, nextTick } from 'vue' |
| | | import dayjs from 'dayjs' |
| | | import PageHeader from '@/components/PageHeader.vue' |
| | | import useUserStore from '@/store/modules/user' |
| | | import { formatDateToYMD } from '@/utils/ruoyi' |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | import { getInPageByCustom } from "@/api/inventoryManagement/stockIn.js"; |
| | | import { stockOut } from "@/api/inventoryManagement/stockManage.js"; |
| | | |
| | | const userStore = useUserStore() |
| | | const { proxy } = getCurrentInstance() |
| | | |
| | | // è¿åä¸ä¸é¡µ |
| | | const goBack = () => { |
| | | uni.navigateBack() |
| | | } |
| | | |
| | | // æå¼æ¥æéæ©å¨ï¼ç®åå¯é ï¼ |
| | | const openDatePickerHandler = () => { |
| | | // è¥å·²æé䏿¥æï¼ç¨å®åå§åï¼å¦åç¨ä»å¤© |
| | | dateValue.value = searchForm.value.timeStr |
| | | ? dayjs(searchForm.value.timeStr, 'YYYY-MM-DD').valueOf() |
| | | : Date.now() |
| | | showDatePicker.value = true |
| | | } |
| | | |
| | | // æ¥æéæ©ç¡®è®¤ï¼ä¸å
¶ä»é¡µä¸è´ï¼æ¿æ¶é´æ³ -> YYYY-MM-DDï¼ |
| | | const onDateConfirm = (e) => { |
| | | searchForm.value.timeStr = formatDateToYMD(e.value) |
| | | showDatePicker.value = false |
| | | handleQuery() |
| | | } |
| | | |
| | | // ååºå¼æ°æ® |
| | | const tableData = ref([]) |
| | | const userList = ref([]) |
| | | const tableLoading = ref(false) |
| | | const dialogFormVisible = ref(false) |
| | | const showDatePicker = ref(false) |
| | | const showOutDatePicker = ref(false) |
| | | const loadStatus = ref('loadmore') |
| | | const dateValue = ref(new Date().getTime()) |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | | size: 10, |
| | | }) |
| | | |
| | | const total = ref(0) |
| | | const currentRowId = ref(null) |
| | | const currentRowNum = ref(0) |
| | | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | supplierName: '', |
| | | timeStr: '', |
| | | }, |
| | | form: { |
| | | inboundQuantity: '', |
| | | inboundTime: '', |
| | | nickName: '', |
| | | }, |
| | | rules: { |
| | | inboundQuantity: [ |
| | | { required: true, message: '请è¾å
¥åºåºæ°é', trigger: 'blur' }, |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value && (value <= 0 || value > currentRowNum.value)) { |
| | | callback(new Error('请填å
¥æææ°å')) |
| | | } else { |
| | | callback() |
| | | } |
| | | }, |
| | | trigger: 'blur' |
| | | } |
| | | ], |
| | | inboundTime: [ |
| | | { required: true, message: 'è¯·éæ©åºåºæ¥æ', trigger: 'change' } |
| | | ], |
| | | nickName: [ |
| | | { required: true, message: 'è¯·éæ©åºåºäºº', trigger: 'change' } |
| | | ] |
| | | } |
| | | }) |
| | | |
| | | const { searchForm, form, rules } = toRefs(data) |
| | | |
| | | |
| | | |
| | | // æ¥è¯¢å表 |
| | | const handleQuery = () => { |
| | | page.current = 1 |
| | | getList() |
| | | } |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true |
| | | const params = { |
| | | ...page, |
| | | supplierName: searchForm.value.supplierName, |
| | | timeStr: searchForm.value.timeStr |
| | | } |
| | | |
| | | getInPageByCustom(params).then(res => { |
| | | tableLoading.value = false |
| | | if (page.current === 1) { |
| | | tableData.value = res.data.records || [] |
| | | } else { |
| | | tableData.value = [...tableData.value, ...(res.data.records || [])] |
| | | } |
| | | total.value = res.data.total || 0 |
| | | |
| | | // æ´æ°å è½½ç¶æ |
| | | if (tableData.value.length >= total.value) { |
| | | loadStatus.value = 'nomore' |
| | | } else { |
| | | loadStatus.value = 'loadmore' |
| | | } |
| | | }).catch(() => { |
| | | tableLoading.value = false |
| | | loadStatus.value = 'error' |
| | | }) |
| | | } |
| | | |
| | | // å è½½æ´å¤ |
| | | const loadMore = () => { |
| | | if (loadStatus.value === 'nomore') return |
| | | |
| | | loadStatus.value = 'loading' |
| | | page.current++ |
| | | getList() |
| | | } |
| | | |
| | | // æå¼åºåºè¡¨å |
| | | const openForm = async (row) => { |
| | | dialogFormVisible.value = true |
| | | currentRowId.value = row.id |
| | | currentRowNum.value = row.inboundNum || 0 |
| | | |
| | | // åå§åè¡¨åæ°æ® |
| | | form.value = { |
| | | inboundQuantity: '', |
| | | inboundTime: getCurrentDate(), |
| | | nickName: '', |
| | | } |
| | | |
| | | // å è½½ç¨æ·å表 |
| | | try { |
| | | const userLists = await userListNoPageByTenantId() |
| | | userList.value = userLists.data.map(item => ({ |
| | | ...item, |
| | | label: item.nickName, |
| | | value: item.userId |
| | | })) |
| | | } catch (error) { |
| | | console.error('å è½½ç¨æ·å表失败:', error) |
| | | userList.value = [] |
| | | } |
| | | } |
| | | |
| | | // æäº¤è¡¨å |
| | | const submitForm = () => { |
| | | proxy.$refs.formRef.validate().then(valid => { |
| | | if (valid && currentRowId.value) { |
| | | const outData = { |
| | | id: currentRowId.value, |
| | | salesLedgerProductId: 0, |
| | | quantity: form.value.inboundQuantity, |
| | | time: form.value.inboundTime, |
| | | userId: form.value.nickName, |
| | | type: 3 // èªå®ä¹åºåºç±»å |
| | | } |
| | | |
| | | stockOut(outData).then(res => { |
| | | uni.$u.toast('æäº¤æå') |
| | | closeDia() |
| | | getList() |
| | | }).catch(err => { |
| | | uni.$u.toast('åºåºå¤±è´¥') |
| | | }) |
| | | } |
| | | }).catch(err => { |
| | | console.log('表åéªè¯å¤±è´¥:', err) |
| | | }) |
| | | } |
| | | |
| | | // å
³éå¼¹çª |
| | | const closeDia = () => { |
| | | dialogFormVisible.value = false |
| | | proxy.$refs.formRef.resetFields() |
| | | } |
| | | |
| | | // å¯¼åº |
| | | const handleOut = () => { |
| | | uni.showModal({ |
| | | title: '导åº', |
| | | content: 'æ¯å¦ç¡®è®¤å¯¼åºï¼', |
| | | success: (res) => { |
| | | if (res.confirm) { |
| | | proxy.download("/stockin/exportTwo", {}, 'èªå®ä¹åºåºå°è´¦.xlsx') |
| | | } |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // è·åå½åæ¥æ |
| | | function getCurrentDate() { |
| | | const today = new Date() |
| | | const year = today.getFullYear() |
| | | const month = String(today.getMonth() + 1).padStart(2, '0') |
| | | const day = String(today.getDate()).padStart(2, '0') |
| | | return `${year}-${month}-${day}` |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getList() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .stock-out-page { |
| | | min-height: 100vh; |
| | | background: #f5f5f5; |
| | | padding-bottom: 80px; |
| | | } |
| | | |
| | | .search-section { |
| | | background: #fff; |
| | | padding: 16px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .search-bar { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 12px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .search-input { |
| | | flex: 1; |
| | | } |
| | | |
| | | .search-button { |
| | | width: 44px; |
| | | height: 44px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background: #f5f5f5; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .date-filter { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 12px 16px; |
| | | background: #f5f5f5; |
| | | border-radius: 8px; |
| | | } |
| | | |
| | | .date-text { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | .stock-list { |
| | | padding: 0 16px; |
| | | } |
| | | |
| | | .stock-item { |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | padding: 16px; |
| | | margin-bottom: 12px; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| | | } |
| | | |
| | | .item-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | .item-left { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .batch-icon { |
| | | width: 32px; |
| | | height: 32px; |
| | | background: #2979ff; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .batch-text { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | |
| | | .time-text { |
| | | font-size: 12px; |
| | | color: #999; |
| | | } |
| | | |
| | | .item-details { |
| | | margin: 12px 0; |
| | | } |
| | | |
| | | .detail-row { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 8px 0; |
| | | } |
| | | |
| | | .detail-label { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | |
| | | .detail-value { |
| | | font-size: 14px; |
| | | color: #333; |
| | | text-align: right; |
| | | flex: 1; |
| | | margin-left: 12px; |
| | | } |
| | | |
| | | .detail-value.highlight { |
| | | color: #2979ff; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .detail-value.price { |
| | | color: #ff6b00; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | .item-actions { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin-top: 12px; |
| | | padding-top: 12px; |
| | | border-top: 1px solid #f5f5f5; |
| | | } |
| | | |
| | | .no-data { |
| | | text-align: center; |
| | | padding: 60px 0; |
| | | color: #999; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .load-more { |
| | | padding: 20px 16px; |
| | | } |
| | | |
| | | .popup-content { |
| | | width: 600rpx; |
| | | background: #ffffff; |
| | | border-radius: 20rpx; |
| | | |
| | | .popup-header { |
| | | padding: 40rpx 30rpx 20rpx; |
| | | text-align: center; |
| | | border-bottom: 1rpx solid #f0f0f0; |
| | | |
| | | .popup-title { |
| | | font-size: 32rpx; |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | } |
| | | |
| | | .popup-body { |
| | | padding: 30rpx; |
| | | } |
| | | |
| | | .popup-footer { |
| | | padding: 30rpx; |
| | | display: flex; |
| | | gap: 20rpx; |
| | | border-top: 1rpx solid #f0f0f0; |
| | | |
| | | .u-button { |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | </style> |