spring
7 天以前 add06adc5d974ac685cb637c48f2455034c8a52f
src/pages/procurementManagement/procurementInvoiceLedger/detail.vue
@@ -1,292 +1,335 @@
<template>
   <view class="account-detail">
      <!-- 使用通用页面头部组件 -->
      <PageHeader title="编辑来票台账" @back="goBack" />
      <van-form @submit="submitForm" ref="formRef" label-width="120px" input-align="right" error-message-align="right" scroll-to-error scroll-to-error-position="center">
         <van-cell-group title="基本信息" inset>
            <van-field v-model="form.purchaseContractNumber" label="采购合同号" readonly />
            <van-field v-model="form.salesContractNo" label="销售合同号" readonly />
            <van-field v-model="form.taxInclusiveUnitPrice" label="含税单价(元)" readonly />
            <van-field v-model="form.createdAt" label="创建时间" readonly />
            <van-field v-model="form.invoiceNumber" label="发票号" placeholder="请输入" readonly />
            <van-field v-model="form.ticketsNum" label="来票数" type="number" placeholder="请输入" required :rules="[{ required: true, message: '请输入来票数' }]" @change="inputTicketsNum"/>
            <van-field v-model="form.ticketsAmount" label="本次来票金额(元)" type="number" placeholder="请输入" required :rules="[{ required: true, message: '请输入本次来票金额' }]" @change="inputTicketsAmount"/>
            <view class="tip-text">未来票数:{{ formatAmount(form.futureTickets) }} 元</view>
<!--            <van-field v-model="form.invoicePerson" label="未来票数" readonly />-->
         </van-cell-group>
<!--         <van-cell-group title="附件材料(仅支持 pdf)" inset>-->
<!--            <van-uploader-->
<!--               accept=".pdf"-->
<!--               multiple-->
<!--               :after-read="afterReadUpload"-->
<!--               :before-read="beforeReadPdf"-->
<!--            >-->
<!--               <van-button class="upload-btn" icon="plus" type="primary" block>上传文件</van-button>-->
<!--            </van-uploader>-->
<!--            <view class="uploaded-list" v-if="fileList.length">-->
<!--               <view class="uploaded-item" v-for="(f, idx) in fileList" :key="idx">-->
<!--                  <text class="file-name">{{ f.name || getFileNameFromUrl(f.url) }}</text>-->
<!--                  <van-button size="mini" type="danger" plain @click="removeUploaded(idx)">移除</van-button>-->
<!--               </view>-->
<!--            </view>-->
<!--         </van-cell-group>-->
         <view class="footer-btns">
            <van-button class="cancel-btn" @click="goBack">取消</van-button>
            <van-button class="save-btn" native-type="submit" form-type="submit">保存</van-button>
         </view>
      </van-form>
   </view>
  <view class="account-detail">
    <!-- 使用通用页面头部组件 -->
    <PageHeader title="编辑来票台账"
                @back="goBack" />
    <up-form @submit="submitForm"
             ref="formRef"
             label-width="120"
             :model="form">
      <up-form-item label="采购合同号"
                    prop="purchaseContractNumber">
        <up-input v-model="form.purchaseContractNumber"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="销售合同号"
                    prop="salesContractNo">
        <up-input v-model="form.salesContractNo"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="含税单价(元)"
                    prop="taxInclusiveUnitPrice">
        <up-input v-model="form.taxInclusiveUnitPrice"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="创建时间"
                    prop="createdAt">
        <up-input v-model="form.createdAt"
                  placeholder="自动生成"
                  disabled />
      </up-form-item>
      <up-form-item label="发票号"
                    prop="invoiceNumber">
        <up-input v-model="form.invoiceNumber"
                  placeholder="请输入"
                  disabled />
      </up-form-item>
      <up-form-item label="来票数"
                    prop="ticketsNum"
                    required
                    :rules="rules.ticketsNum">
        <up-input v-model="form.ticketsNum"
                  type="number"
                  placeholder="请输入"
                  @blur="inputTicketsNum" />
      </up-form-item>
      <up-form-item label="本次来票金额(元)"
                    prop="ticketsAmount"
                    required
                    :rules="rules.ticketsAmount">
        <up-input v-model="form.ticketsAmount"
                  type="number"
                  placeholder="请输入"
                  @blur="inputTicketsAmount" />
      </up-form-item>
      <view class="tip-text">未来票数:{{ formatAmount(form.futureTickets) }} </view>
      <!-- 使用公共底部按钮组件 -->
      <FooterButtons show
                     cancelText="取消"
                     confirmText="保存"
                     @cancel="goBack"
                     @confirm="onSubmit" />
    </up-form>
  </view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { showToast, showLoadingToast, closeToast } from 'vant'
import dayjs from 'dayjs'
import useUserStore from '@/store/modules/user'
import { getToken } from '@/utils/auth'
import { invoiceLedgerSaveOrUpdate } from '@/api/salesManagement/invoiceLedger.js'
import config from '@/config.js'
import {getProductRecordById, updateRegistration} from "@/api/procurementManagement/procurementInvoiceLedger";
  import { ref, onMounted } from "vue";
  import dayjs from "dayjs";
  import useUserStore from "@/store/modules/user";
  import { getToken } from "@/utils/auth";
  import { invoiceLedgerSaveOrUpdate } from "@/api/salesManagement/invoiceLedger.js";
  import config from "@/config.js";
  import {
    getProductRecordById,
    updateRegistration,
  } from "@/api/procurementManagement/procurementInvoiceLedger";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
const userStore = useUserStore()
  const userStore = useUserStore();
const formRef = ref()
let form = ref({
   salesLedgerId: '',
   customerId: '',
   invoiceNo: '',
   invoiceTotal: '',
   taxRate: '',
   invoicePerson: '',
   invoiceDate: '',
   customerName: '',
   fileList: [],
   createTime: '',
   taxInclusiveTotalPrice: '',
   taxInclusiveUnitPrice: ''
})
const fileList = ref([])
const currentId = ref('')
const temFutureTickets = ref(0)
  const formRef = ref();
  let form = ref({
    salesLedgerId: "",
    customerId: "",
    invoiceNo: "",
    invoiceTotal: "",
    taxRate: "",
    invoicePerson: "",
    invoiceDate: "",
    customerName: "",
    fileList: [],
    createTime: "",
    taxInclusiveTotalPrice: "",
    taxInclusiveUnitPrice: "",
  });
  const fileList = ref([]);
  const currentId = ref("");
  const temFutureTickets = ref(0);
  const originalTicketsNum = ref(0); // 保存原始来票数
// 日期选择
const currentInvoiceDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()])
  // 表单校验规则 - 使用简单的 required 规则
  const rules = {
    ticketsNum: [{ required: true, message: "请输入来票数", trigger: "blur" }],
    ticketsAmount: [
      { required: true, message: "请输入本次来票金额", trigger: "blur" },
    ],
  };
const goBack = () => {
   uni.removeStorageSync('invoiceLedgerEditRow');
   uni.navigateBack()
}
const inputTicketsNum = (val) => {
   // 确保含税单价存在且不为零
   if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) {
      showToast("含税单价不能为零或未定义");
      return;
   }
   if (Number(form.value.ticketsNum) > Number(temFutureTickets.value)) {
      showToast("来票数不得大于未来票数");
      form.value.ticketsNum = temFutureTickets.value
   }
   // 确保所有数值都转换为数字类型进行计算
   const ticketsAmount = Number(form.value.ticketsNum) * Number(form.value.taxInclusiveUnitPrice);
   const futureTickets = Number(temFutureTickets.value) - Number(form.value.ticketsNum);
   form.value.futureTickets = Number(futureTickets.toFixed(2));
   form.value.ticketsAmount = Number(ticketsAmount.toFixed(2));
};
const inputTicketsAmount = (val) => {
   // 确保含税单价存在且不为零
   if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) {
      showToast("含税单价不能为零或未定义");
      return;
   }
   if (Number(val) > Number(form.value.futureTickets*form.value.taxInclusiveUnitPrice)) {
      showToast("本次来票金额不得大于总金额");
      form.value.ticketsAmount = (form.value.futureTickets*form.value.taxInclusiveUnitPrice).toFixed(2)
      const ticketsNum = Number(form.value.ticketsAmount) / Number(form.value.taxInclusiveUnitPrice);
      form.value.ticketsNum = Number(ticketsNum.toFixed(2))
      return;
   }
   // 确保所有数值都转换为数字类型进行计算
   const ticketsNum = Number(val) / Number(form.value.taxInclusiveUnitPrice);
   form.value.ticketsNum = Number(ticketsNum.toFixed(2));
};
const formatAmount = (val) => {
   if (val === undefined || val === null || val === '') return '0.00'
   const num = Number(val)
   if (Number.isNaN(num)) return '0.00'
   return num.toFixed(2)
}
  const goBack = () => {
    uni.removeStorageSync("invoiceLedgerEditRow");
    uni.navigateBack();
  };
  const inputTicketsNum = () => {
    // 处理空值情况
    if (!form.value.ticketsNum || form.value.ticketsNum === "") {
      form.value.ticketsNum = 0;
      return;
    }
    // 确保含税单价存在且不为零
    if (
      !form.value.taxInclusiveUnitPrice ||
      Number(form.value.taxInclusiveUnitPrice) === 0
    ) {
      uni.showToast({
        title: "含税单价不能为零或未定义",
        icon: "none",
      });
      return;
    }
    const newTicketsNum = Number(form.value.ticketsNum) || 0;
    // 计算总可用票数:原始未来票数 + 原始来票数
    const totalAvailableTickets = Number(temFutureTickets.value) + Number(originalTicketsNum.value);
    // 验证来票数不能大于总可用票数
    if (newTicketsNum > totalAvailableTickets) {
      uni.showToast({
        title: "来票数不得大于总可用票数",
        icon: "none",
      });
      form.value.ticketsNum = totalAvailableTickets;
      // 重新计算未来票数
      const futureTickets = totalAvailableTickets - totalAvailableTickets;
      form.value.futureTickets = Number(futureTickets.toFixed(2));
      form.value.ticketsAmount = Number((totalAvailableTickets * Number(form.value.taxInclusiveUnitPrice)).toFixed(2));
      return;
    }
// 上传前校验(兼容 Vant Uploader 的 file/fileList 结构)
const beforeReadPdf = (file) => {
   const items = Array.isArray(file) ? file : [file]
   for (const it of items) {
      const raw = it?.file || it
      const fileName = raw?.name || it?.name || ''
      const ext = fileName.split('.').pop()?.toLowerCase()
      const sizeOk = (raw?.size || 0) <= 10 * 1024 * 1024
      if (ext !== 'pdf') {
         showToast('仅支持pdf文件')
         return false
      }
      if (!sizeOk) {
         showToast('上传文件大小不能超过10MB')
         return false
      }
   }
   return true
}
    // 确保所有数值都转换为数字类型进行计算
    const ticketsAmount =
      newTicketsNum * Number(form.value.taxInclusiveUnitPrice);
    // 计算未来票数:总可用票数 - 新来票数
    const futureTickets = totalAvailableTickets - newTicketsNum;
    form.value.futureTickets = Number(futureTickets.toFixed(2));
    form.value.ticketsAmount = Number(ticketsAmount.toFixed(2));
  };
  const inputTicketsAmount = () => {
    // 处理空值情况
    if (!form.value.ticketsAmount || form.value.ticketsAmount === "") {
      form.value.ticketsAmount = 0;
    }
    // 确保含税单价存在且不为零
    if (
      !form.value.taxInclusiveUnitPrice ||
      Number(form.value.taxInclusiveUnitPrice) === 0
    ) {
      uni.showToast({
        title: "含税单价不能为零或未定义",
        icon: "none",
      });
      return;
    }
const uploadSingleFile = async (fileObj) => {
   return new Promise((resolve, reject) => {
      showLoadingToast({ message: '正在上传...' })
      const baseUrl = config.baseUrl + '/invoiceLedger/uploadFile'
    const newTicketsAmount = Number(form.value.ticketsAmount) || 0;
    // 计算总可用金额:原始未来票数 + 原始来票数
    const totalAvailableTickets = Number(temFutureTickets.value) + Number(originalTicketsNum.value);
    const totalAvailableAmount = totalAvailableTickets * Number(form.value.taxInclusiveUnitPrice);
      const filePath = fileObj?.url || fileObj?.tempFilePath || fileObj?.file?.path
      if (filePath) {
         uni.uploadFile({
            url: baseUrl,
            filePath,
            name: 'file',
            header: { Authorization: 'Bearer ' + getToken() },
            success: (res) => {
               closeToast()
               try {
                  const data = JSON.parse(res.data || '{}')
                  if (data.code === 200) {
                     resolve(data.data)
                  } else {
                     reject(new Error(data.msg || '上传失败'))
                  }
               } catch (err) {
                  reject(err)
               }
            },
            fail: (err) => {
               closeToast()
               reject(err)
            }
         })
         return
      }
    if (newTicketsAmount > totalAvailableAmount) {
      uni.showToast({
        title: "本次来票金额不得大于总金额",
        icon: "none",
      });
      form.value.ticketsAmount = totalAvailableAmount.toFixed(2);
      const ticketsNum =
        Number(form.value.ticketsAmount) /
        Number(form.value.taxInclusiveUnitPrice);
      form.value.ticketsNum = Number(ticketsNum.toFixed(2));
      // 更新未来票数
      const futureTickets = totalAvailableTickets - form.value.ticketsNum;
      form.value.futureTickets = Number(futureTickets.toFixed(2));
      return;
    }
      // H5: 使用原始 File(input 选择)
      const rawFile = fileObj?.file
      if (rawFile) {
         // uni.uploadFile 在 H5 不支持原生 File 对象,这里用 fetch 发送 FormData
         const formData = new FormData()
         formData.append('file', rawFile, rawFile.name || 'file.pdf')
         formData.append('salesLedgerId', form.value.salesLedgerId || currentId.value || '')
         fetch(baseUrl, {
            method: 'POST',
            headers: { Authorization: 'Bearer ' + getToken() },
            body: formData
         }).then(async (res) => {
            closeToast()
            const data = await res.json()
            if (data.code === 200) {
               resolve(data.data)
            } else {
               reject(new Error(data.msg || '上传失败'))
            }
         }).catch((err) => {
            closeToast()
            reject(err)
         })
         return
      }
    // 确保所有数值都转换为数字类型进行计算
    const ticketsNum =
      newTicketsAmount / Number(form.value.taxInclusiveUnitPrice);
    form.value.ticketsNum = Number(ticketsNum.toFixed(2));
    // 更新未来票数
    const futureTickets = totalAvailableTickets - form.value.ticketsNum;
    form.value.futureTickets = Number(futureTickets.toFixed(2));
  };
  const formatAmount = val => {
    if (val === undefined || val === null || val === "") return "0.00";
    const num = Number(val);
    if (Number.isNaN(num)) return "0.00";
    return num.toFixed(2);
  };
      closeToast()
      reject(new Error('未找到可上传的文件'))
   })
}
  const loadDetail = async (id, purchaseLedgerId, productModelId) => {
    try {
      uni.showLoading({
        title: "加载中...",
      });
      const res = await getProductRecordById({
        id: id,
        purchaseLedgerId: purchaseLedgerId,
        productModelId: productModelId,
      });
      const data = res?.data || res;
      form.value = { ...data };
      temFutureTickets.value = data.futureTickets;
      originalTicketsNum.value = Number(data.ticketsNum) || 0; // 保存原始来票数
      fileList.value = data?.fileList || [];
      if (!form.value.invoicePerson) {
        form.value.invoicePerson = userStore.nickName;
      }
      if (!form.value.invoiceDate) {
        form.value.invoiceDate = dayjs().format("YYYY-MM-DD");
      }
      uni.hideLoading();
    } catch (e) {
      uni.hideLoading();
      uni.showToast({
        title: "加载失败",
        icon: "none",
      });
    }
  };
const afterReadUpload = async (file) => {
   try {
      const files = Array.isArray(file) ? file : file?.file ? [file] : [file]
      for (const f of files) {
         const uploaded = await uploadSingleFile(f)
         fileList.value.push(uploaded)
      }
      showToast('上传成功')
   } catch (e) {
      showToast('上传失败')
   }
}
  const submitForm = async () => {
    try {
      // 提交表单的具体逻辑
      await updateRegistration(form.value);
      uni.showToast({
        title: "提交成功",
        icon: "success",
      });
      setTimeout(() => {
        goBack();
      }, 800);
    } catch (e) {
      uni.showToast({
        title: "提交失败,请重试",
        icon: "none",
      });
    }
  };
const removeUploaded = (index) => {
   fileList.value.splice(index, 1)
}
  // 表单提交
  const onSubmit = async () => {
    // 在验证前,确保必填字段有值
    if (!form.value.ticketsNum || form.value.ticketsNum === "" || form.value.ticketsNum === null || form.value.ticketsNum === undefined) {
      uni.showToast({
        title: "请输入来票数",
        icon: "none",
      });
      return;
    }
    if (!form.value.ticketsAmount || form.value.ticketsAmount === "" || form.value.ticketsAmount === null || form.value.ticketsAmount === undefined) {
      uni.showToast({
        title: "请输入本次来票金额",
        icon: "none",
      });
      return;
    }
    // 确保字段是数字类型,并转换为字符串(因为表单可能需要字符串类型)
    const ticketsNum = Number(form.value.ticketsNum);
    const ticketsAmount = Number(form.value.ticketsAmount);
    // 如果来票数为0或来票金额为0,提示用户
    if (isNaN(ticketsNum) || ticketsNum <= 0) {
      uni.showToast({
        title: "来票数必须大于0",
        icon: "none",
      });
      return;
    }
    if (isNaN(ticketsAmount) || ticketsAmount <= 0) {
      uni.showToast({
        title: "本次来票金额必须大于0",
        icon: "none",
      });
      return;
    }
    // 更新表单值,确保是有效的数字字符串
    form.value.ticketsNum = ticketsNum.toString();
    form.value.ticketsAmount = ticketsAmount.toString();
    // 手动验证通过后,直接提交,跳过表单验证(避免真机上的验证问题)
    submitForm();
  };
  const purchaseLedgerId = ref("");
  const productModelId = ref({});
const getFileNameFromUrl = (url) => {
   try { if (!url) return ''; return decodeURIComponent(url.split('/').pop()) } catch (e) { return url }
}
const loadDetail = async (id) => {
   try {
      showLoadingToast({ message: '加载中...' })
      const res = await getProductRecordById({ id })
      const data = res?.data || res
      form.value = { ...data }
      temFutureTickets.value = data.futureTickets;
      fileList.value = data?.fileList || []
      if (!form.value.invoicePerson) {
         form.value.invoicePerson = userStore.nickName
      }
      if (!form.value.invoiceDate) {
         form.value.invoiceDate = dayjs().format('YYYY-MM-DD')
      }
      closeToast()
   } catch (e) {
      closeToast()
      showToast('加载失败')
   }
}
const submitForm = async () => {
   try {
      showLoadingToast({ message: '提交中...' })
      await updateRegistration(form.value)
      closeToast()
      showToast('提交成功')
      setTimeout(() => { goBack() }, 800)
   } catch (e) {
      closeToast()
      showToast('提交失败,请重试')
   }
}
onMounted(() => {
   const rowStr = uni.getStorageSync('invoiceLedgerEditRow')
   if (rowStr) {
      try {
         const row = JSON.parse(rowStr)
         currentId.value = row.id
         loadDetail(currentId.value)
      } catch (e) {
         // ignore
      }
   }
})
  onMounted(() => {
    const rowStr = uni.getStorageSync("invoiceLedgerEditRow");
    if (rowStr) {
      try {
        const row = JSON.parse(rowStr);
        currentId.value = row.id;
        purchaseLedgerId.value = row.purchaseLedgerId;
        productModelId.value = row.productModelId;
        loadDetail(currentId.value, purchaseLedgerId.value, productModelId.value);
      } catch (e) {
        // ignore
      }
    }
  });
</script>
<style scoped lang="scss">
.account-detail {
   min-height: 100vh;
   background: #f8f9fa;
   padding-bottom: 5rem;
}
.uploaded-list { padding: 8px 16px 0 16px; }
.uploaded-item { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }
.file-name { font-size: 12px; color: #333; margin-right: 8px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tip-text { padding: 4px 16px 0 16px; font-size: 12px; color: #888; }
.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; }
.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; }
  @import "@/static/scss/form-common.scss";
</style>