| | |
| | | <!-- 使用通用页面头部组件 --> |
| | | <PageHeader title="编辑来票台账" @back="goBack" /> |
| | | |
| | | <u-form @submit="submitForm" ref="formRef" label-width="120" input-align="right" error-message-align="right"> |
| | | <u-cell-group title="基本信息"> |
| | | <u-form-item label="采购合同号" border-bottom> |
| | | <u-input v-model="form.purchaseContractNumber" readonly /> |
| | | </u-form-item> |
| | | <u-form-item label="销售合同号" border-bottom> |
| | | <u-input v-model="form.salesContractNo" readonly /> |
| | | </u-form-item> |
| | | <u-form-item label="含税单价(元)" border-bottom> |
| | | <u-input v-model="form.taxInclusiveUnitPrice" readonly /> |
| | | </u-form-item> |
| | | <u-form-item label="创建时间" border-bottom> |
| | | <u-input v-model="form.createdAt" readonly /> |
| | | </u-form-item> |
| | | <u-form-item label="发票号" border-bottom> |
| | | <u-input v-model="form.invoiceNumber" placeholder="请输入" readonly /> |
| | | </u-form-item> |
| | | <u-form-item label="来票数" prop="ticketsNum" required border-bottom> |
| | | <u-input v-model="form.ticketsNum" type="number" placeholder="请输入" @change="inputTicketsNum"/> |
| | | </u-form-item> |
| | | <u-form-item label="本次来票金额(元)" prop="ticketsAmount" required border-bottom> |
| | | <u-input v-model="form.ticketsAmount" type="number" placeholder="请输入" @change="inputTicketsAmount"/> |
| | | </u-form-item> |
| | | <view class="tip-text">未来票数:{{ formatAmount(form.futureTickets) }} 元</view> |
| | | </u-cell-group> |
| | | <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> |
| | | |
| | | <view class="footer-btns"> |
| | | <u-button class="cancel-btn" @click="goBack">取消</u-button> |
| | | <u-button class="save-btn" type="primary" @click="submitForm">保存</u-button> |
| | | </view> |
| | | </u-form> |
| | | <!-- 使用公共底部按钮组件 --> |
| | | <FooterButtons |
| | | show |
| | | cancelText="取消" |
| | | confirmText="保存" |
| | | @cancel="goBack" |
| | | @confirm="onSubmit" |
| | | /> |
| | | </up-form> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | // 替换 Vant 的 toast 方法 |
| | | // 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"; |
| | | |
| | | // 替换 toast 方法 |
| | | const showToast = (message) => { |
| | | uni.showToast({ |
| | | title: message, |
| | | icon: 'none' |
| | | }) |
| | | } |
| | | |
| | | const showLoadingToast = (message) => { |
| | | uni.showLoading({ |
| | | title: message || '加载中...' |
| | | }) |
| | | } |
| | | |
| | | const closeToast = () => { |
| | | uni.hideLoading() |
| | | } |
| | | import PageHeader from '@/components/PageHeader.vue'; |
| | | import FooterButtons from '@/components/FooterButtons.vue'; |
| | | |
| | | const userStore = useUserStore() |
| | | |
| | |
| | | const currentId = ref('') |
| | | const temFutureTickets = ref(0) |
| | | |
| | | // 日期选择 |
| | | const currentInvoiceDate = ref([new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate()]) |
| | | // 表单校验规则 |
| | | const rules = { |
| | | ticketsNum: [ |
| | | { required: true, message: '请输入来票数', trigger: 'blur' } |
| | | ], |
| | | ticketsAmount: [ |
| | | { required: true, message: '请输入本次来票金额', trigger: 'blur' } |
| | | ] |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | uni.removeStorageSync('invoiceLedgerEditRow'); |
| | | uni.navigateBack() |
| | | } |
| | | const inputTicketsNum = (val) => { |
| | | const inputTicketsNum = () => { |
| | | // 确保含税单价存在且不为零 |
| | | if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) { |
| | | showToast("含税单价不能为零或未定义"); |
| | | uni.showToast({ |
| | | title: "含税单价不能为零或未定义", |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | if (Number(form.value.ticketsNum) > Number(temFutureTickets.value)) { |
| | | showToast("来票数不得大于未来票数"); |
| | | uni.showToast({ |
| | | title: "来票数不得大于未来票数", |
| | | icon: 'none' |
| | | }); |
| | | form.value.ticketsNum = temFutureTickets.value |
| | | } |
| | | |
| | |
| | | form.value.futureTickets = Number(futureTickets.toFixed(2)); |
| | | form.value.ticketsAmount = Number(ticketsAmount.toFixed(2)); |
| | | }; |
| | | const inputTicketsAmount = (val) => { |
| | | const inputTicketsAmount = () => { |
| | | // 确保含税单价存在且不为零 |
| | | if (!form.value.taxInclusiveUnitPrice || Number(form.value.taxInclusiveUnitPrice) === 0) { |
| | | showToast("含税单价不能为零或未定义"); |
| | | uni.showToast({ |
| | | title: "含税单价不能为零或未定义", |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | if (Number(val) > Number(form.value.futureTickets*form.value.taxInclusiveUnitPrice)) { |
| | | showToast("本次来票金额不得大于总金额"); |
| | | if (Number(form.value.ticketsAmount) > Number(form.value.futureTickets*form.value.taxInclusiveUnitPrice)) { |
| | | uni.showToast({ |
| | | title: "本次来票金额不得大于总金额", |
| | | icon: 'none' |
| | | }); |
| | | 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)) |
| | |
| | | } |
| | | |
| | | // 确保所有数值都转换为数字类型进行计算 |
| | | const ticketsNum = Number(val) / Number(form.value.taxInclusiveUnitPrice); |
| | | const ticketsNum = Number(form.value.ticketsAmount) / Number(form.value.taxInclusiveUnitPrice); |
| | | form.value.ticketsNum = Number(ticketsNum.toFixed(2)); |
| | | }; |
| | | const formatAmount = (val) => { |
| | |
| | | return num.toFixed(2) |
| | | } |
| | | |
| | | // 上传前校验(兼容 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 uploadSingleFile = async (fileObj) => { |
| | | return new Promise((resolve, reject) => { |
| | | showLoadingToast({ message: '正在上传...' }) |
| | | const baseUrl = config.baseUrl + '/invoiceLedger/uploadFile' |
| | | |
| | | 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 |
| | | } |
| | | |
| | | // 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 |
| | | } |
| | | |
| | | closeToast() |
| | | reject(new Error('未找到可上传的文件')) |
| | | }) |
| | | } |
| | | |
| | | 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 removeUploaded = (index) => { |
| | | fileList.value.splice(index, 1) |
| | | } |
| | | |
| | | const getFileNameFromUrl = (url) => { |
| | | try { if (!url) return ''; return decodeURIComponent(url.split('/').pop()) } catch (e) { return url } |
| | | } |
| | | |
| | | const loadDetail = async (id) => { |
| | | try { |
| | | showLoadingToast({ message: '加载中...' }) |
| | | uni.showLoading({ |
| | | title: '加载中...' |
| | | }); |
| | | const res = await getProductRecordById({ id }) |
| | | const data = res?.data || res |
| | | form.value = { ...data } |
| | |
| | | if (!form.value.invoiceDate) { |
| | | form.value.invoiceDate = dayjs().format('YYYY-MM-DD') |
| | | } |
| | | closeToast() |
| | | uni.hideLoading(); |
| | | } catch (e) { |
| | | closeToast() |
| | | showToast('加载失败') |
| | | uni.hideLoading(); |
| | | uni.showToast({ |
| | | title: '加载失败', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | |
| | | const submitForm = async () => { |
| | | try { |
| | | showLoadingToast({ message: '提交中...' }) |
| | | // 提交表单的具体逻辑 |
| | | await updateRegistration(form.value) |
| | | closeToast() |
| | | showToast('提交成功') |
| | | uni.showToast({ |
| | | title: '提交成功', |
| | | icon: 'success' |
| | | }); |
| | | setTimeout(() => { goBack() }, 800) |
| | | } catch (e) { |
| | | closeToast() |
| | | showToast('提交失败,请重试') |
| | | uni.showToast({ |
| | | title: '提交失败,请重试', |
| | | icon: 'none' |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // 表单提交 |
| | | const onSubmit = () => { |
| | | formRef.value.validate().then(() => { |
| | | // 表单验证通过,提交表单 |
| | | submitForm(); |
| | | }).catch(error => { |
| | | // 表单验证失败 |
| | | console.log('表单验证失败', error); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | const rowStr = uni.getStorageSync('invoiceLedgerEditRow') |
| | |
| | | // 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> |
| | | |