huangxw 1 день назад
Родитель
Сommit
4cef2a775b

+ 7 - 0
src/pages.json

@@ -420,6 +420,13 @@
                         "navigationBarTitleText": "成品销售登记"
                     }
                 },
+                // 选择销售批次
+                {
+                    "path": "finished-product/select-sales-batch/index",
+                    "style": {
+                        "navigationBarTitleText": "选择销售批次"
+                    }
+                },
                 // 销售登记记录清单
                 {
                     "path": "finished-product/sales-record-list/index",

+ 10 - 2
src/plant/models/warehouseCard/product-item.vue

@@ -1,9 +1,12 @@
 <template>
-    <view class="b-radius bg-#fff pd-20 p-rtv" @click.stop="handleClick">
+    <view class="b-radius pd-20 p-rtv" @click.stop="handleClick">
         <!-- 顶部类型标签和日期 -->
         <view class="d-flex j-sb a-c li-item-head mb-16">
             <view :class="['li-left-tag', typeBgClass]">{{ selectDictLabel(pt_product_type, item.instoreBizInfo?.productType) }}</view>
-            <view class="f-s-22 c-#666">{{ item?.productDate || '' }}</view>
+             <view v-if="showDate" class="f-s-22 c-#666">
+                {{ getDateRangeFrt(item?.productDate, item?.productDateEnd) }}
+                <span>包装</span>
+            </view>
         </view>
         <!-- 主标题和状态 -->
         <view class="d-flex flex1 mb-10">
@@ -77,6 +80,11 @@ const props = defineProps({
         type: Boolean,
         default: false,
     },
+    // 是否显示右上角日期
+    showDate: {
+        type: Boolean,
+        default: true,
+    },
 });
 
 const emit = defineEmits<{

+ 115 - 2
src/plant/storage/finished-product/sales-record-list/index.vue

@@ -1,3 +1,116 @@
 <template>
-    <view>xx</view>
-</template>
+    <z-paging ref="paging" v-model="list" bgColor="#f7f7f7" @query="query">
+        <template #top>
+            <ut-navbar title="销售清单" :fixed="false" :breadcrumb="false"></ut-navbar>
+        </template>
+        <view class="pd3-24-24-0">
+            <ut-search ref="searchRef" v-model="form.keyword" @search="onRefresh" margin="0" :border="false" placeholder="搜订单号、购货单位名称或代码" bgColor="#fff" height="86rpx" borderRadius="10rpx"></ut-search>
+        </view>
+        <view class="pd-24 bg-#f7f7f7">
+            <up-swipe-action>
+                <up-swipe-action-item v-for="(item, index) in list" :key="index" :name="item?.id" :disabled="+item?.bgStatus" :options="optionsActionTemp" @click="clickTempSwipe($event, item)" class="mb-20 b-radius">
+                    <view class="b-radius bg-#fff p-rtv pd-24"> </view>
+                </up-swipe-action-item>
+            </up-swipe-action>
+        </view>
+    </z-paging>
+</template>
+<script setup lang="ts">
+import { useClientRequest } from '@/utils/request';
+import { formItemBtnListStyle } from '@/assets/styles/uview-plus';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { pt_pack_ref_type } = toRefs<any>(proxy?.useDict('pt_pack_ref_type'));
+const list = ref<any[]>();
+const form = ref({ keyword: '' });
+const paging = ref();
+const rowId = ref('');
+const showBatchInfo = ref(false);
+
+const query = async (pageNum: number, pageSize: number) => {
+    const params = {
+        pageNum,
+        pageSize,
+        ...form.value,
+    };
+    const res = await useClientRequest.get('/plt-api/app/saleOrder/list', params);
+    if (res) {
+        const { rows } = res;
+        paging.value.complete(rows);
+    }
+};
+// 暂存项左滑删除配置
+const optionsActionTemp = reactive([
+    {
+        text: '删除',
+        style: {
+            backgroundColor: '#F74C30',
+        },
+    },
+]);
+// 暂存项删除点击(本地移除)
+const clickTempSwipe = async (event: object, item: any) => {
+    const { name, index } = event as any;
+    if (index === 0) {
+        try {
+            const res = await uni.showModal({
+                title: '撤回提示',
+                content: '您确定要撤回该销售订单吗?',
+                confirmColor: '#F74C30',
+            });
+            if (!res.confirm) return;
+            await uni.showLoading({
+                title: '撤回中...',
+                mask: true,
+            });
+            await useClientRequest.get(`/plt-api/app/saleOrder/withdraw/${name}`);
+            uni.hideLoading();
+            uni.showToast({
+                title: '撤回成功',
+                icon: 'success',
+            });
+            paging.value?.reload();
+        } catch (error) {
+            console.error('撤回销售订单失败:', error);
+        }
+    }
+};
+
+const onRefresh = () => {
+    try {
+        paging.value?.reload();
+    } catch (error) {
+        console.error('刷新列表失败:', error);
+    }
+};
+onMounted(() => {
+    uni.$on('refreshSaleOrderList', () => {
+        onRefresh();
+    });
+});
+</script>
+<style lang="scss" scoped>
+.search-select-item {
+    height: 86rpx;
+    background-color: #fff;
+    border-radius: 10rpx;
+    box-sizing: border-box;
+    padding: 12rpx;
+}
+.tag-span {
+    padding: 4rpx 12rpx;
+    font-size: 20rpx;
+    border-radius: 18rpx;
+}
+.li-item-head {
+    margin-left: -24rpx;
+    margin-top: -24rpx;
+}
+.li-left-tag {
+    padding: 6rpx 16rpx;
+    color: #fff;
+    border-radius: 16rpx 0 16rpx 0;
+    font-size: 20rpx;
+    font-weight: 500;
+}
+</style>

+ 293 - 36
src/plant/storage/finished-product/sales-register/index.vue

@@ -5,58 +5,260 @@
         </template>
         <up-form class="p-rtv bg-#fff" labelPosition="top" :model="form" :rules="rules" labelWidth="auto" ref="upFormRef">
             <view class="pd-24 bg-#fff mb-10">
-                <view class="h-1" id="inputStorageIdpppp"></view>
-                <up-form-item borderBottom label="选择包装对象" prop="inputStorageId" required>
+                <view class="h-1" id="detailListpppp"></view>
+                <up-form-item borderBottom label="销售批次" prop="detailList" required>
                     <view class="flex1">
-                        <up-button v-if="!form.inputStorageId" @click="selectStorage" type="primary" plain>
+                        <template v-for="(item, index) in form.detailList" :key="index">
+                            <view class="bg-#FBFDFB border-#A9D7B4 b-radius mb-10 p-rtv">
+                                <view class="border-bottom-#A9D7B4">
+                                    <ProductItem :item="selectedStorageMap[item]" :showDate="false" :hideExtraInfo="true"></ProductItem>
+                                </view>
+                                <view class="pd-24">
+                                    <!-- 嘻嘻嘻 -->
+                                    <view class="d-flex j-sb mb-10">
+                                        <view class="f-s-28 c-#333 f-w-500">本次销售量:</view>
+                                        <view>
+                                            <up-checkbox @change="changeRowChecked($event, item)" shape="circle" :customStyle="{ marginBottom: '8px' }" label="全部销售" name="agree" usedAlone v-model:checked="selectedStorageFormMap[item].aloneChecked"> </up-checkbox>
+                                        </view>
+                                    </view>
+                                    <view v-if="+selectedStorageMap[item]?.restAmount" class="d-flex">
+                                        <view class="flex1 ov-hd f-s-28 c-#666">包装规格:{{ selectedStorageMap[item].specn }}</view>
+                                        <view class="d-flex f-s-28 c-#666">
+                                            <view>销售量:</view>
+                                            <view class="d-flex a-c border-bottom-#E6E6E6 flex1 ov-hd">
+                                                <up-input class="w-180" maxlength="10" inputAlign="center" v-model="selectedStorageFormMap[item].proCount" @change="changeRowInput(item)" placeholder="请输入" border="none"></up-input>
+                                                <view class="c-#666 ml-5">{{ selectedStorageMap[item].unit }}</view>
+                                            </view>
+                                        </view>
+                                    </view>
+                                    <view v-if="+selectedStorageMap[item]?.restRestAmount" class="d-flex">
+                                        <view class="flex1 ov-hd f-s-28 c-#666">包装规格:{{ selectedStorageMap[item].restSpecn }}</view>
+                                        <view class="d-flex f-s-28 c-#666">
+                                            <view>销售量:</view>
+                                            <view class="d-flex a-c border-bottom-#E6E6E6 flex1 ov-hd">
+                                                <up-input class="w-180" maxlength="10" inputAlign="center" v-model="selectedStorageFormMap[item].restFlag" @change="changeRowInput(item)" placeholder="请输入" border="none"></up-input>
+                                                <view class="c-#666 ml-5">{{ selectedStorageMap[item].unit }}</view>
+                                            </view>
+                                        </view>
+                                    </view>
+                                </view>
+                                <view class="close-icon pd-16" @click="deleteStorage(item, index)">
+                                    <up-icon color="#F81242" name="close" size="32rpx"></up-icon>
+                                </view>
+                            </view>
+                        </template>
+                        <up-button @click="selectStorage" type="primary" plain>
                             <image class="w-36 h-36 mr-10" src="https://yujin-szyy.oss-cn-chengdu.aliyuncs.com/szyy/images-plt/common/select_push_icon.png" mode="widthFix" />
-                            <span>请选择您要包装的对象</span>
+                            <span>请选择要销售的成品批次</span>
                         </up-button>
                     </view>
                 </up-form-item>
-                <view class="h-1" id="storageUseAmountpppp"></view>
-                <up-form-item borderBottom label="本次包装用量" prop="storageUseAmount" required>
-                    <up-input v-model="form.storageUseAmount" placeholder="请输入本次包装用量" border="none" clearable></up-input>
+                <!-- 出库日期 -->
+                <view class="h-1" id="outstoreDatepppp"></view>
+                <ut-datetime-picker v-model="form.outstoreDate" :maxDate="new Date()" mode="date">
+                    <up-form-item borderBottom label="出库日期" required prop="outstoreDate">
+                        <up-input v-model="form.outstoreDate" readonly placeholder="请选择出库日期" border="none" clearable></up-input>
+                        <template #right>
+                            <up-icon size="22rpx" color="#37A954" name="arrow-down-fill"></up-icon>
+                        </template>
+                    </up-form-item>
+                </ut-datetime-picker>
+                <!-- 入库批号 -->
+                <view class="h-1" id="orderNopppp"></view>
+                <up-form-item borderBottom label="销售订单号" required prop="orderNo">
+                    <up-input v-model="form.orderNo" placeholder="请输入销售订单号" border="none" clearable></up-input>
                     <template #right>
-                        <span class="f-s-30 f-w-5 c-#333">{{ form?.storageUseUnit || 'kg' }}</span>
+                        <up-button @click="generateBatchCode" type="primary" :customStyle="formItemBtnStyle">系统生成</up-button>
                     </template>
                 </up-form-item>
+                <up-form-item borderBottom label="购货单位" prop="customerId" required>
+                    <view class="flex1">
+                        <ContactUnitInput v-model="form.customerId" v-model:info="form.customerInfo" :params="{ cpyType: '3' }" title="选择购货单位" placeholder="请选择购货单位信息"></ContactUnitInput>
+                    </view>
+                </up-form-item>
+                <!-- 随货同行单号 -->
+                <view class="h-1" id="shdNopppp"></view>
+                <up-form-item borderBottom label="随货同行单号" prop="shdNo">
+                    <up-input v-model="form.shdNo" placeholder="请输入随货同行单号" border="none" clearable></up-input>
+                </up-form-item>
+                <!-- 随货同行单 -->
+                <up-form-item label="随货同行单" prop="shdImg" borderBottom>
+                    <ut-upload v-model="form.shdImg" :max-count="9"></ut-upload>
+                </up-form-item>
+                <!-- 放行单 -->
+                <up-form-item label="放行单" prop="fxdImg" borderBottom>
+                    <ut-upload v-model="form.fxdImg" :max-count="9"></ut-upload>
+                </up-form-item>
+                <!-- 放行人默认登录人 -->
+                <up-form-item label="放行人" prop="fxrName" borderBottom>
+                    <up-input v-model="form.fxrName" placeholder="请输入放行人姓名" border="none" clearable></up-input>
+                </up-form-item>
+                <!-- 运输方式字典选择 -->
+                <ut-action-sheet v-model="form.transportMode" :tabs="transportation_mode" mode="custom" title="请选择运输方式">
+                    <up-form-item borderBottom label="运输方式" prop="transportMode">
+                        <view v-if="form.transportMode" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(transportation_mode, form.transportMode) }}</view>
+                        <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择运输方式</view>
+                        <template #right>
+                            <up-icon size="22rpx" color="#37A954" name="arrow-down-fill"></up-icon>
+                        </template>
+                    </up-form-item>
+                </ut-action-sheet>
+                <up-form-item borderBottom label="备注" prop="remark">
+                    <up-textarea v-model="form.remark" placeholder="请输入备注" autoHeight></up-textarea>
+                </up-form-item>
             </view>
         </up-form>
+        <view class="pd2-40-24">
+            <up-button type="primary" @click="submit">确认登记</up-button>
+        </view>
     </z-paging>
 </template>
 <script setup lang="ts">
 import { useClientRequest } from '@/utils/request';
 import { formItemBtnStyle } from '@/assets/styles/uview-plus';
 import NP from 'number-precision';
+import { useInfoStore } from '@/store';
+import { parseTime } from '@/utils/ruoyi';
+import ProductItem from '@/plant/models/warehouseCard/product-item.vue';
+import ContactUnitInput from '@/models/contact-unit-input/contact-unit-input.vue';
+interface SelectedStorageItem {
+    id: string | number;
+    [key: string]: any;
+}
+
+const infoStore = useInfoStore();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { pt_pack_ref_type, pt_pack_spec_unit, pt_expire_date_unit } = toRefs<any>(proxy?.useDict('pt_pack_ref_type', 'pt_pack_spec_unit', 'pt_expire_date_unit'));
+const { transportation_mode } = toRefs<any>(proxy?.useDict('transportation_mode'));
 const upFormRef = ref();
 const paging = ref();
 const form = ref({
-   
+    detailList: [] as string[],
+    outstoreDate: parseTime(new Date(), '{y}-{m}-{d}'),
+    orderNo: '',
+    shdNo: '',
+    shdImg: '',
+    fxdImg: '',
+    fxrName: infoStore.userInfo?.name,
+    transportMode: '',
 });
+const selectedStorageMap = ref<Record<string, SelectedStorageItem>>({});
+const selectedStorageFormMap = ref<Record<string, any>>({});
+const callbackName = 'selectSalesStorageBatch';
+const selectingStorage = ref(false);
+const handleSelectStorage = (data: SelectedStorageItem[] = []) => {
+    const nextStorageMap = { ...selectedStorageMap.value };
+    const nextDetailList = [...form.value.detailList];
+    data.forEach((item) => {
+        if (!item?.id && item?.id !== 0) {
+            return;
+        }
+        const itemId = String(item.id);
+        if (!nextDetailList.includes(itemId)) {
+            nextDetailList.push(itemId);
+            nextStorageMap[itemId] = item;
+            selectedStorageFormMap.value[itemId] = {
+                storageId: item.id,
+                proCount: '',
+                restFlag: '',
+                // 是否全部销售
+                aloneChecked: false,
+            };
+        }
+    });
+
+    form.value.detailList = nextDetailList;
+    selectedStorageMap.value = nextStorageMap;
+    selectingStorage.value = false;
+    uni.$off(callbackName);
+};
 
 const rules = reactive<Record<string, any>>({
-   
+    detailList: [
+        { type: 'array', required: true, message: '请选择销售批次' },
+        {
+            validator: (rule: any, value: string[], callback: any) => {
+                if (!value || value.length === 0) {
+                    callback(new Error('请选择销售批次'));
+                } else {
+                    for (const itemId of value) {
+                        const storageItem = selectedStorageMap.value[String(itemId)];
+                        const formItem = selectedStorageFormMap.value[String(itemId)];
+                        if (!storageItem || !formItem) {
+                            callback(new Error('请选择销售批次'));
+                            return;
+                        }
+                        const restAmount = Number(storageItem.restAmount || 0);
+                        const proCount = Number(formItem.proCount || 0);
+                        const restFlag = Number(formItem.restFlag || 0);
+                        if (formItem.aloneChecked) {
+                            if (restAmount > 0 && proCount !== restAmount) {
+                                callback(new Error('请勾选全部销售或输入正确的销售量'));
+                                return;
+                            }
+                            if (restAmount === 0 && restFlag !== 0) {
+                                callback(new Error('请勾选全部销售或输入正确的销售量'));
+                                return;
+                            }
+                        } else {
+                            if ((proCount && proCount <= 0) || (restFlag && restFlag <= 0)) {
+                                callback(new Error('请输入正确的销售量'));
+                                return;
+                            }
+                            if (proCount > restAmount) {
+                                callback(new Error('销售量不能大于剩余量'));
+                                return;
+                            }
+                            if (restFlag > storageItem.restRestAmount) {
+                                callback(new Error('销售量不能大于剩余量'));
+                                return;
+                            }
+                        }
+                    }
+                    callback();
+                }
+            },
+        },
+    ],
+    outstoreDate: [{ required: true, message: '请选择出库日期' }],
+    orderNo: [{ required: true, message: '请输入销售订单号' }],
 });
-
 const submit = () => {
-    uni.$u.debounce(async () => {
-        try {
-            await upFormRef.value?.validate();
-        } catch (error: any) {
-            const firstErrorField = error && error[0].field + 'pppp';
-            paging.value?.scrollIntoViewById(firstErrorField, 30, true);
-            return;
-        }
-
-        try {
-           
-        } catch (e) {
-            console.error('销售登记失败:', e);
-        }
-    }, 500, true);
+    uni.$u.debounce(
+        async () => {
+            try {
+                await upFormRef.value?.validate();
+            } catch (error: any) {
+                const firstErrorField = error && error[0].field + 'pppp';
+                paging.value?.scrollIntoViewById(firstErrorField, 30, true);
+                return;
+            }
+            try {
+                const submitData = {
+                    ...form.value,
+                    detailList: form.value.detailList.map((item) => ({
+                        storageId: selectedStorageFormMap.value[String(item)].storageId,
+                        proCount: selectedStorageFormMap.value[String(item)].proCount,
+                        restFlag: selectedStorageFormMap.value[String(item)].restFlag || undefined,
+                    })),
+                };
+                await useClientRequest.post('/plt-api/app/saleOrder/add', submitData);
+                uni.showToast({
+                    title: '销售登记成功',
+                    icon: 'success',
+                });
+                uni.$emit('refreshStorageRoomList');
+                await new Promise((resolve) => setTimeout(resolve, 500));
+                uni.$u.route({
+                    type: 'redirect',
+                    url: '/plant/storage/finished-product/sales-registration/index',
+                });
+            } catch (e) {
+                console.error('销售登记失败:', e);
+            }
+        },
+        500,
+        true,
+    );
 };
 // 点击随机生成服务端生成唯一的批号
 const generateBatchCode = async () => {
@@ -64,28 +266,83 @@ const generateBatchCode = async () => {
         title: '生成中...',
     });
     const res = await useClientRequest.post('/plt-api/app/plantationTask/getBatchCode', {
-        plType: 'Z',
-        linkType: 'I',
+        plType: '',
+        linkType: '1',
     });
     if (res && res.code === 200) {
         uni.hideLoading();
-        form.value.packSn = res.data;
+        form.value.orderNo = res.data;
         uni.showToast({
-            title: '号生成成功',
+            title: '销售订单号生成成功',
             icon: 'success',
         });
     }
 };
 // 去选择包装对象页选择包装对象,选择后返回并赋值
 const selectStorage = () => {
-    uni.$on('selectStorageObject', (data: any) => {
-      
+    selectingStorage.value = true;
+    uni.$off(callbackName);
+    uni.$on(callbackName, handleSelectStorage);
+    uni.$u.route({
+        type: 'navigateTo',
+        url: '/plant/storage/finished-product/select-sales-batch/index',
+        params: {
+            callback: callbackName,
+            selectedIds: JSON.stringify(form.value.detailList),
+        },
     });
 };
-const deleteStorage = () => {
-  
+const deleteStorage = (item: string, index: number) => {
+    const itemId = String(item);
+    form.value.detailList.splice(index, 1);
+    delete selectedStorageMap.value[itemId];
+    delete selectedStorageFormMap.value[itemId];
+};
+
+const changeRowInput = (item: string) => {
+    const itemId = String(item);
+    const storageItem = selectedStorageMap.value[itemId];
+    const formItem = selectedStorageFormMap.value[itemId];
+    if (!storageItem || !formItem) {
+        return;
+    }
+
+    const restAmount = Number(storageItem.restAmount || 0);
+    const restRestAmount = Number(storageItem.restRestAmount || 0);
+    const hasProAmount = restAmount > 0;
+    const hasRestAmount = restRestAmount > 0;
+    const hasProInput = formItem.proCount !== '' && formItem.proCount !== null && formItem.proCount !== undefined;
+    const hasRestInput = formItem.restFlag !== '' && formItem.restFlag !== null && formItem.restFlag !== undefined;
+    const proChecked = !hasProAmount || (hasProInput && Number(formItem.proCount) === restAmount);
+    const restChecked = !hasRestAmount || (hasRestInput && Number(formItem.restFlag) === restRestAmount);
+
+    formItem.aloneChecked = proChecked && restChecked;
+};
+
+const changeRowChecked = (event: any, item: string) => {
+    const itemId = String(item);
+    const checked = typeof event === 'boolean' ? event : !!event;
+    selectedStorageFormMap.value[itemId].aloneChecked = checked;
+    if (checked) {
+        // 选中
+        selectedStorageFormMap.value[itemId].proCount = selectedStorageMap.value[itemId]?.restAmount || '';
+        selectedStorageFormMap.value[itemId].restFlag = selectedStorageMap.value[itemId]?.restRestAmount || '';
+    } else {
+        // 取消选中
+        selectedStorageFormMap.value[itemId].proCount = '';
+        selectedStorageFormMap.value[itemId].restFlag = '';
+    }
 };
-onLoad((options: any) => {});
+onHide(() => {
+    if (selectingStorage.value) {
+        return;
+    }
+    uni.$off(callbackName);
+});
+
+onUnload(() => {
+    uni.$off(callbackName);
+});
 </script>
 <style lang="scss" scoped>
 .close-icon {

+ 9 - 13
src/plant/storage/finished-product/sales-registration/index.vue

@@ -14,11 +14,11 @@
         <view class="pd-24">
             <!-- 两个并排按钮 -->
             <view class="d-flex gap-24 mb-24">
-                <up-button text="继续销售登记" type="primary" class="flex1" :custom-style="{ backgroundColor: '#14c4c4', borderColor: '#14c4c4' }" @click="continueRegistration"></up-button>
-                <up-button text="查看销售清单" type="success" class="flex1" :custom-style="{ backgroundColor: '#41c06d', borderColor: '#41c06d' }" @click="viewSalesList"></up-button>
+                <up-button text="继续销售登记" class="flex1" color="#14c4c4"  @click="continueRegistration"></up-button>
+                <up-button text="查看销售清单" type="primary" class="flex1"  @click="viewSalesList"></up-button>
             </view>
             <!-- 返回列表按钮 -->
-            <up-button text="返回列表" type="text" :custom-style="{ backgroundColor: '#ffffff', borderColor: '#41c06d', color: '#41c06d', width: '100%' }" @click="backToList"></up-button>
+            <up-button @click="navigateBackOrHome()" text="返回列表" type="primary" :custom-style="{ backgroundColor: '#fff' }" plain></up-button>
         </view>
     </z-paging>
 </template>
@@ -29,24 +29,20 @@ const paging = ref();
 
 // 继续销售登记
 const continueRegistration = () => {
-    uni.navigateBack({
-        delta: 1,
+    uni.$u.route({
+        type: 'redirect',
+        url: '/plant/storage/finished-product/sales-register/index',
     });
 };
 
 // 查看销售清单
 const viewSalesList = () => {
-    uni.reLaunch({
-        url: '/plant/storage/finished-product/list/index',
+    uni.$u.route({
+        type: 'redirect',
+        url: '/plant/storage/finished-product/sales-record-list/index',
     });
 };
 
-// 返回列表
-const backToList = () => {
-    uni.reLaunch({
-        url: '/plant/storage/finished-product/list/index',
-    });
-};
 </script>
 
 <style lang="scss" scoped>

+ 187 - 0
src/plant/storage/finished-product/select-sales-batch/index.vue

@@ -0,0 +1,187 @@
+<template>
+    <z-paging ref="paging" v-model="list" paging-class="paging-btm-shadow" bgColor="#f7f7f7" safe-area-inset-bottom @query="query">
+        <template #top>
+            <ut-navbar title="请选择销售批次" :fixed="false" :breadcrumb="false"></ut-navbar>
+            <view class="d-flex a-c pd3-24-24-0">
+                <view class="min-w-230 flex1">
+                    <ut-action-sheet v-model="form.productType" :tabs="tabs_pt_product_type" mode="custom" @change="onRefresh" title="选择入库类型">
+                        <view class="d-flex search-select-item a-c">
+                            <view class="flex1 ov-hd f-s-28 c-333 text-center f-w-5 w-s-no">{{ selectDictLabel(pt_product_type, form.productType) || '全部' }} </view>
+                            <up-icon size="24rpx" color="#333" name="arrow-down-fill" class="mr-5"></up-icon>
+                        </view>
+                    </ut-action-sheet>
+                </view>
+                <view class="h-86 pl-20 w-100%">
+                    <ut-search ref="searchRef" v-model="form.keyword" @search="changeSeach" margin="0" :border="false" placeholder="搜名称、批号、库房、供应商" bgColor="#fff" height="86rpx" borderRadius="10rpx"></ut-search>
+                </view>
+            </view>
+        </template>
+
+        <view class="pd-24 bg-#f7f7f7">
+            <view v-for="item in list" :key="item.id" :class="['select-item-card', { active: isSelected(item), disabled: isDisabled(item) }]" class="mb-20 p-rtv bg-#fff">
+                <image v-if="isSelected(item)" class="w-40 h-40 checked-icon" src="https://yujin-szyy.oss-cn-chengdu.aliyuncs.com/szyy/images-lm/price/checked1.png" mode="widthFix" />
+                <view v-if="isDisabled(item)" class="disabled-tag">已选择</view>
+                <product-item :item="item" :hideExtraInfo="true" @click="toggleSelection(item)" />
+            </view>
+        </view>
+
+        <template #empty>
+            <view class="d-flex flex-cln a-c" style="margin-top: -200rpx">
+                <ut-empty class="mg-at" color="#ccc" size="28rpx">暂无可选销售批次</ut-empty>
+            </view>
+        </template>
+
+        <template #bottom>
+            <view v-if="selectedList.length > 0" class="bg-#EBF6EE c-primary f-s-24 pd-10 pl-24">已选择 {{ selectedList.length }} 个批次</view>
+            <view class="pd-20 d-flex">
+                <up-button type="primary" @click="confirmSelection">确认选择</up-button>
+            </view>
+        </template>
+    </z-paging>
+</template>
+
+<script setup lang="ts">
+import { useClientRequest } from '@/utils/request';
+import ProductItem from '@/plant/models/warehouseCard/product-item.vue';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { pt_product_type } = toRefs<any>(proxy?.useDict('pt_product_type'));
+interface BatchItem {
+    id: number | string;
+    [key: string]: any;
+}
+
+const paging = ref();
+const list = ref<BatchItem[]>([]);
+const selectedList = ref<BatchItem[]>([]);
+const disabledIds = ref<string[]>([]);
+const opts = ref({
+    callback: 'selectSalesStorageBatch',
+});
+
+const form = ref({
+    keyword: '',
+    restFlag: '1',
+    storageType: '3',
+    productType: '',
+});
+const tabs_pt_product_type = computed(() => {
+    return [{ label: '全部', value: '' }, ...pt_product_type.value];
+});
+const query = async (pageNum: number, pageSize: number) => {
+    const res = await useClientRequest.get('/plt-api/app/storage/list', {
+        pageNum,
+        pageSize,
+        ...form.value,
+    });
+
+    if (res?.code === 200) {
+        paging.value.complete(res.rows || []);
+        return;
+    }
+
+    paging.value.complete(false);
+};
+
+const normalizeId = (id: number | string | null | undefined) => String(id ?? '');
+
+const isDisabled = (item: BatchItem) => disabledIds.value.includes(normalizeId(item.id));
+
+const isSelected = (item: BatchItem) => selectedList.value.some((selected) => normalizeId(selected.id) === normalizeId(item.id));
+
+const toggleSelection = (item: BatchItem) => {
+    if (isDisabled(item)) {
+        uni.showToast({
+            title: '该销售批次已选择',
+            icon: 'none',
+        });
+        return;
+    }
+
+    const index = selectedList.value.findIndex((selected) => normalizeId(selected.id) === normalizeId(item.id));
+    if (index > -1) {
+        selectedList.value.splice(index, 1);
+        return;
+    }
+
+    selectedList.value.push(item);
+};
+
+const confirmSelection = () => {
+    if (!selectedList.value.length) {
+        uni.showToast({
+            title: '请选择至少一个销售批次',
+            icon: 'none',
+        });
+        return;
+    }
+
+    uni.$emit(opts.value.callback, selectedList.value);
+    uni.navigateBack({
+        delta: 1,
+    });
+};
+
+const onRefresh = () => {
+    paging.value?.reload();
+};
+
+onLoad((options: any) => {
+    opts.value.callback = options?.callback || 'selectSalesStorageBatch';
+
+    if (!options?.selectedIds) {
+        return;
+    }
+
+    try {
+        disabledIds.value = (JSON.parse(decodeURIComponent(options.selectedIds)) || []).map((id: number | string) => normalizeId(id));
+    } catch (error) {
+        disabledIds.value = [];
+    }
+});
+</script>
+
+<style lang="scss" scoped>
+.search-select-item {
+    height: 86rpx;
+    background-color: #fff;
+    border-radius: 10rpx;
+    box-sizing: border-box;
+    padding: 12rpx;
+}
+
+.select-item-card {
+    position: relative;
+    border: 1rpx solid transparent;
+    border-radius: 16rpx;
+    overflow: hidden;
+
+    &.active {
+        border-color: $u-primary;
+        box-shadow: 0 0 0 2rpx rgba(55, 169, 84, 0.08);
+        background-color: #FBFDFB;
+    }
+
+    &.disabled {
+        opacity: 0.5;
+    }
+
+    .checked-icon {
+        position: absolute;
+        right: 0;
+        bottom: 0;
+        z-index: 2;
+    }
+
+    .disabled-tag {
+        position: absolute;
+        right: 20rpx;
+        top: 40rpx;
+        z-index: 2;
+        padding: 4rpx 12rpx;
+        border-radius: 999rpx;
+        font-size: 22rpx;
+        color: #999;
+        background-color: #ececec;
+    }
+}
+</style>

+ 4 - 0
src/tools/select-code-section/index.vue

@@ -118,6 +118,10 @@ onLoad((options: any) => {
         selectedIds.value = [];
     }
 });
+onUnload(() => {
+    console.log('????');
+    uni.$off(opts.value.callback || 'refreshCodesRange');
+});
 </script>
 <style lang="scss" scoped>
 .select-item-card {

+ 1 - 1
unocss.config.js

@@ -181,7 +181,7 @@ export default defineConfig({
         ],
         //边框
         [
-            /^border-([\w#-]+)$/,
+            /^border-(?!top-|bottom-|w-)([\w#-]+)$/,
             ([_, color]) => ({
                 'border-color': color,
                 'border-style': 'solid',