| | |
| | | <template> |
| | | <view class="account-detail"> |
| | | <!-- 使用通用页面头部组件 --> |
| | | <PageHeader title="台账详情" @back="goBack" /> |
| | | |
| | | <!-- 表单区域 --> |
| | | <u-form @submit="onSubmit" label-width="110" input-align="right" style="margin-top: 10px" error-message-align="right"> |
| | | <u-form-item label="采购合同号" prop="purchaseContractNumber" required border-bottom> |
| | | <u-input v-model="form.purchaseContractNumber" placeholder="自动生成" /> |
| | | </u-form-item> |
| | | <u-form-item label="销售合同号" prop="salesContractNo" required border-bottom> |
| | | <u-input |
| | | v-model="form.salesContractNo" |
| | | readonly |
| | | placeholder="点击选择销售合同号" |
| | | @click="showPicker = true" |
| | | /> |
| | | </u-form-item> |
| | | <u-form-item label="供应商名称" prop="supplierName" required border-bottom> |
| | | <u-input |
| | | v-model="form.supplierName" |
| | | readonly |
| | | placeholder="点击选择供应商" |
| | | @click="showCustomerPicker = true" |
| | | /> |
| | | </u-form-item> |
| | | <u-form-item label="项目名称" prop="projectName" required border-bottom> |
| | | <u-input v-model="form.projectName" placeholder="请输入项目名称" /> |
| | | </u-form-item> |
| | | <u-form-item label="付款方式" border-bottom> |
| | | <u-input v-model="form.paymentMethod" placeholder="请输入付款方式" /> |
| | | </u-form-item> |
| | | <u-form-item label="录入人" border-bottom> |
| | | <u-input v-model="form.recorderName" placeholder="请输入" disabled /> |
| | | </u-form-item> |
| | | <u-form-item label="录入日期" border-bottom> |
| | | <u-input v-model="form.entryDate" placeholder="请输入" disabled /> |
| | | </u-form-item> |
| | | <u-popup v-model="showPicker" mode="bottom"> |
| | | <u-picker |
| | | :columns="salesContractList" |
| | | v-model="pickerValue" |
| | | @confirm="onConfirm" |
| | | @cancel="showPicker = false" |
| | | /> |
| | | </u-popup> |
| | | <u-popup v-model="showCustomerPicker" mode="bottom"> |
| | | <u-picker |
| | | :columns="supplierList" |
| | | v-model="pickerCustomerValue" |
| | | @confirm="onCustomerConfirm" |
| | | @cancel="showCustomerPicker = false" |
| | | /> |
| | | </u-popup> |
| | | |
| | | <!-- 产品大类选择器 --> |
| | | <u-popup v-model="showCategoryPicker" mode="bottom"> |
| | | <!-- 头部按钮区域 --> |
| | | <view class="popup-header"> |
| | | <view @click="showCategoryPicker = false" class="cancelButton">取消</view> |
| | | <view @click="confirmCategorySelection" class="confirmButton">确定</view> |
| | | </view> |
| | | <up-tree |
| | | :data="productOptions" |
| | | :props="defaultProps" |
| | | show-checkbox |
| | | default-expand-all |
| | | check-strictly |
| | | @check-change="onCategoryConfirm" |
| | | /> |
| | | </u-popup> |
| | | |
| | | <!-- 规格型号选择器 --> |
| | | <u-popup v-model="showSpecificationPicker" mode="bottom"> |
| | | <u-picker |
| | | :columns="modelOptions" |
| | | v-model="pickerSpecificationValue" |
| | | @confirm="onSpecificationConfirm" |
| | | @cancel="showSpecificationPicker = false" |
| | | /> |
| | | </u-popup> |
| | | |
| | | <!-- 税率选择器 --> |
| | | <u-popup v-model="showTaxRatePicker" mode="bottom"> |
| | | <u-picker |
| | | :columns="taxRateOptions" |
| | | v-model="pickerTaxRateValue" |
| | | @confirm="onTaxRateConfirm" |
| | | @cancel="showTaxRatePicker = false" |
| | | /> |
| | | </u-popup> |
| | | |
| | | <!-- 发票类型选择器 --> |
| | | <u-popup v-model="showInvoiceTypePicker" mode="bottom"> |
| | | <u-picker |
| | | :columns="invoiceTypeOptions" |
| | | v-model="pickerInvoiceTypeValue" |
| | | @confirm="onInvoiceTypeConfirm" |
| | | @cancel="showInvoiceTypePicker = false" |
| | | /> |
| | | </u-popup> |
| | | <!-- 产品信息 --> |
| | | <view class="product-section"> |
| | | <view class="section-header"> |
| | | <text class="section-title">产品信息</text> |
| | | <u-button type="primary" size="small" @click="addProduct" class="add-btn" v-if="operationType !== 'view'"> |
| | | <u-icon name="plus" size="14" /> |
| | | 新增 |
| | | </u-button> |
| | | </view> |
| | | <view class="product-card" v-for="(product, idx) in productData" :key="idx"> |
| | | <!-- 产品类 --> |
| | | <view class="product-header"> |
| | | <view class="product-title"> |
| | | <u-icon name="file-text" color="#2979ff" size="15" /> |
| | | <text class="product-productCategory">产品 {{ idx + 1 }}</text> |
| | | </view> |
| | | <!-- 操作按钮 --> |
| | | <view class="product-actions" v-if="operationType !== 'view'"> |
| | | <u-button type="error" size="mini" @click="removeProduct(idx)" class="del-btn"> |
| | | <u-icon name="trash" size="12" /> |
| | | 删除 |
| | | </u-button> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- 产品信息表单 --> |
| | | <view class="product-form"> |
| | | <!-- 产品大类 --> |
| | | <view class="product-category" prop="productCategory" required border-bottom> |
| | | <u-input |
| | | v-model="product.productCategory" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openCategoryPicker(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 规格型号 --> |
| | | <view class="product-specificationModel" prop="specificationModel" required border-bottom> |
| | | <u-input |
| | | v-model="product.specificationModel" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openSpecificationPicker(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 单位 --> |
| | | <view class="product-unit" prop="unit" required border-bottom> |
| | | <u-input |
| | | v-model="product.unit" |
| | | placeholder="请输入" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 税率 --> |
| | | <view class="product-taxRate" prop="taxRate" required border-bottom> |
| | | <u-input |
| | | v-model="product.taxRate" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openTaxRatePicker(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 含税单价 --> |
| | | <view class="product-taxInclusiveUnitPrice" prop="taxInclusiveUnitPrice" required border-bottom> |
| | | <u-input |
| | | v-model="product.taxInclusiveUnitPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatTaxPrice(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 数量 --> |
| | | <view class="product-quantity" prop="quantity" required border-bottom> |
| | | <u-input |
| | | v-model="product.quantity" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatAmount(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 含税总价 --> |
| | | <view class="product-taxInclusiveTotalPrice" prop="taxInclusiveTotalPrice" required border-bottom> |
| | | <u-input |
| | | v-model="product.taxInclusiveTotalPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatTaxTotal(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 不含税总价 --> |
| | | <view class="product-taxExclusiveTotalPrice" prop="taxExclusiveTotalPrice" required border-bottom> |
| | | <u-input |
| | | v-model="product.taxExclusiveTotalPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatNoTaxTotal(idx)" |
| | | /> |
| | | </view> |
| | | |
| | | <!-- 发票类型 --> |
| | | <view class="product-invoiceType" prop="invoiceType" required border-bottom> |
| | | <u-input |
| | | v-model="product.invoiceType" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openInvoiceTypePicker(idx)" |
| | | /> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <view class="footer-btns" v-if="operationType !== 'view'"> |
| | | <u-button class="cancel-btn" @click="goBack">取消</u-button> |
| | | <u-button class="save-btn" type="primary" @click="onSubmit">保存</u-button> |
| | | </view> |
| | | </u-form> |
| | | <PageHeader title="台账详情" |
| | | @back="goBack" /> |
| | | <!-- 表单区域 --> |
| | | <up-form @submit="onSubmit" |
| | | label-width="110" |
| | | ref="formRef" |
| | | :rules="rules" |
| | | :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" |
| | | required |
| | | @click="showPicker = true"> |
| | | <up-input v-model="form.salesContractNo" |
| | | readonly="" |
| | | @click="showPicker = true" |
| | | placeholder="点击选择销售合同号" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="供应商名称" |
| | | prop="supplierName" |
| | | required |
| | | @click="showCustomerPicker = true"> |
| | | <up-input v-model="form.supplierName" |
| | | readonly="" |
| | | @click="showCustomerPicker = true" |
| | | placeholder="点击选择供应商" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showCustomerPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="项目名称" |
| | | prop="projectName" |
| | | required> |
| | | <up-input v-model="form.projectName" |
| | | placeholder="请输入项目名称" /> |
| | | </up-form-item> |
| | | <up-form-item label="付款方式" |
| | | prop="paymentMethod"> |
| | | <up-input v-model="form.paymentMethod" |
| | | placeholder="请输入付款方式" /> |
| | | </up-form-item> |
| | | <up-form-item label="签订日期" |
| | | required |
| | | prop="executionDate"> |
| | | <up-input v-model="form.executionDate" |
| | | placeholder="请选择" |
| | | readonly="" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTimePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="录入人" |
| | | prop="recorderName"> |
| | | <up-input v-model="form.recorderName" |
| | | placeholder="请输入" |
| | | disabled /> |
| | | </up-form-item> |
| | | <up-form-item label="录入日期" |
| | | prop="entryDate"> |
| | | <up-input v-model="form.entryDate" |
| | | 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" |
| | | @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> |
| | | <up-popup :show="showTimePicker" |
| | | mode="bottom" |
| | | @close="showTimePicker = false"> |
| | | <up-datetime-picker :show="true" |
| | | v-model="currentDate" |
| | | @confirm="onDateConfirm" |
| | | @cancel="showTimePicker = false" |
| | | mode="date" /> |
| | | </up-popup> |
| | | <!-- 销售合同号选择 --> |
| | | <up-action-sheet :show="showPicker" |
| | | :actions="salesContractActionList" |
| | | title="选择销售合同号" |
| | | @select="onSalesmanSelect" |
| | | @close="showPicker = false" /> |
| | | <!-- 供应商选择 --> |
| | | <up-action-sheet :show="showCustomerPicker" |
| | | :actions="supplierActionList" |
| | | title="选择供应商" |
| | | @select="onCustomerSelect" |
| | | @close="showCustomerPicker = false" /> |
| | | <!-- 产品大类选择器 --> |
| | | <up-popup :show="showCategoryPicker" |
| | | mode="bottom"> |
| | | <!-- 头部按钮区域 --> |
| | | <view class="popup-header"> |
| | | <view @click="showCategoryPicker = false" |
| | | class="cancelButton">取消</view> |
| | | <view @click="confirmCategorySelection" |
| | | class="confirmButton">确定</view> |
| | | </view> |
| | | <u-tree :data="productOptions" |
| | | :props="defaultProps" |
| | | show-checkbox |
| | | default-expand-all |
| | | check-strictly |
| | | @check-change="onCategoryConfirm" /> |
| | | </up-popup> |
| | | <!-- 规格型号选择器 --> |
| | | <up-action-sheet :show="showSpecificationPicker" |
| | | :actions="specificationActionList" |
| | | title="选择规格型号" |
| | | @select="onSpecificationSelect" |
| | | @close="showSpecificationPicker = false" /> |
| | | <!-- 税率选择器 --> |
| | | <up-action-sheet :show="showTaxRatePicker" |
| | | :actions="taxRateActionList" |
| | | title="选择税率" |
| | | @select="onTaxRateSelect" |
| | | @close="showTaxRatePicker = false" /> |
| | | <!-- 发票类型选择器 --> |
| | | <up-action-sheet :show="showInvoiceTypePicker" |
| | | :actions="invoiceTypeActionList" |
| | | title="选择发票类型" |
| | | @select="onInvoiceTypeSelect" |
| | | @close="showInvoiceTypePicker = false" /> |
| | | <!-- 产品信息 --> |
| | | <view class="product-section"> |
| | | <view class="section-header"> |
| | | <view> |
| | | <text class="section-title">产品信息</text> |
| | | </view> |
| | | <view> |
| | | <up-button type="primary" |
| | | size="small" |
| | | @click="addProduct" |
| | | class="add-btn" |
| | | v-if="operationType !== 'view'"> |
| | | 新增 |
| | | </up-button> |
| | | </view> |
| | | </view> |
| | | <view class="product-card" |
| | | v-for="(product, idx) in productData" |
| | | :key="idx"> |
| | | <!-- 产品类 --> |
| | | <view class="product-header"> |
| | | <view class="product-title"> |
| | | <up-icon name="file-text" |
| | | size="16" |
| | | color="#2979ff"></up-icon> |
| | | <text class="product-productCategory">产品 {{ idx + 1 }}</text> |
| | | </view> |
| | | <!-- 操作按钮 --> |
| | | <view class="product-actions" |
| | | v-if="operationType !== 'view'"> |
| | | <up-button type="error" |
| | | size="mini" |
| | | @click="removeProduct(idx)" |
| | | class="del-btn"> |
| | | 删除 |
| | | </up-button> |
| | | </view> |
| | | </view> |
| | | <!-- 产品信息表单 --> |
| | | <view class="product-form"> |
| | | <!-- 产品大类 --> |
| | | <up-form-item label="产品大类" |
| | | prop="productCategory" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.productCategory" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openCategoryPicker(idx)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showCategoryPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <!-- 规格型号 --> |
| | | <up-form-item label="规格型号" |
| | | prop="specificationModel" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.specificationModel" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openSpecificationPicker(idx)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showSpecificationPicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <!-- 单位 --> |
| | | <up-form-item label="单位" |
| | | prop="unit" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.unit" |
| | | placeholder="请输入" /> |
| | | </up-form-item> |
| | | <!-- 税率 --> |
| | | <up-form-item label="税率(%)" |
| | | prop="taxRate" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.taxRate" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openTaxRatePicker(idx)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTaxRatePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <!-- 含税单价 --> |
| | | <up-form-item label="含税单价(元)" |
| | | prop="taxInclusiveUnitPrice" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.taxInclusiveUnitPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatTaxPrice(idx)" /> |
| | | </up-form-item> |
| | | <!-- 数量 --> |
| | | <up-form-item label="数量" |
| | | prop="quantity" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.quantity" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatAmount(idx)" /> |
| | | </up-form-item> |
| | | <!-- 含税总价 --> |
| | | <up-form-item label="含税总价(元)" |
| | | prop="taxInclusiveTotalPrice" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.taxInclusiveTotalPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatTaxTotal(idx)" /> |
| | | </up-form-item> |
| | | <!-- 不含税总价 --> |
| | | <up-form-item label="不含税总价(元)" |
| | | prop="taxExclusiveTotalPrice" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.taxExclusiveTotalPrice" |
| | | type="number" |
| | | placeholder="请输入" |
| | | @blur="formatNoTaxTotal(idx)" /> |
| | | </up-form-item> |
| | | <!-- 发票类型 --> |
| | | <up-form-item label="发票类型" |
| | | prop="invoiceType" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.invoiceType" |
| | | readonly |
| | | placeholder="请选择" |
| | | @click="openInvoiceTypePicker(idx)" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showInvoiceTypePicker = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <!-- 库存预警数量 --> |
| | | <up-form-item label="库存预警数量" |
| | | prop="warnNum" |
| | | required |
| | | :rules="productRules"> |
| | | <up-input v-model="product.warnNum" |
| | | type="number" |
| | | placeholder="请输入" /> |
| | | </up-form-item> |
| | | <up-form-item label="是否质检" |
| | | prop="invoiceType" |
| | | required |
| | | :rules="productRules"> |
| | | <u-radio-group v-model="product.isChecked" |
| | | placement="row" |
| | | @change="groupChange"> |
| | | <u-radio :customStyle="{marginRight: '40rpx'}" |
| | | label="是" |
| | | :name="true"> |
| | | </u-radio> |
| | | <u-radio label="否" |
| | | :name="false"> |
| | | </u-radio> |
| | | </u-radio-group> |
| | | </up-form-item> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | <!-- 使用公共底部按钮组件 --> |
| | | <FooterButtons :show="operationType !== 'view'" |
| | | cancelText="取消" |
| | | confirmText="保存" |
| | | @cancel="goBack" |
| | | @confirm="onSubmit" /> |
| | | </up-form> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import {onMounted, ref} from 'vue'; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import {calculateTaxExclusiveTotalPrice} from "@/utils/summarizeTable"; |
| | | import { |
| | | addOrEditPurchase, createPurchaseNo, |
| | | getOptions, |
| | | getPurchaseById, |
| | | getSalesNo |
| | | } from "@/api/procurementManagement/procurementLedger"; |
| | | import { onMounted, ref, computed } from "vue"; |
| | | import { modelList, productTreeList } from "@/api/basicData/product.js"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | import { calculateTaxExclusiveTotalPrice } from "@/utils/summarizeTable"; |
| | | import { formatDateToYMD } from "@/utils/ruoyi"; |
| | | import { |
| | | addOrEditPurchase, |
| | | createPurchaseNo, |
| | | getOptions, |
| | | getPurchaseById, |
| | | getSalesNo, |
| | | approveProcessGetInfo, |
| | | } from "@/api/procurementManagement/procurementLedger"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { userListNoPageByTenantId } from "@/api/system/user"; |
| | | // 获取页面参数 |
| | | const operationType = ref(""); |
| | | const editData = ref(null); |
| | | const formRef = ref(null); |
| | | |
| | | // 获取页面参数 |
| | | const operationType = ref(''); |
| | | const editData = ref(null); |
| | | |
| | | const userStore = useUserStore() |
| | | const form = ref({ |
| | | id: '', |
| | | salesContractNo: '', |
| | | purchaseContractNumber: '', |
| | | supplierId: '', |
| | | supplierName: '', |
| | | projectName: '', |
| | | paymentMethod: '', |
| | | recorderId: '', |
| | | recorderName: '', |
| | | entryDate: '', |
| | | }); |
| | | const pickerValue = ref(['']); |
| | | const pickerDateValue = ref([]); |
| | | const showPicker = ref(false); |
| | | const pickerCustomerValue = ref(['']); |
| | | const showCustomerPicker = ref(false); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const productData = ref([]); |
| | | |
| | | // 选择器相关变量 |
| | | const showCategoryPicker = ref(false); |
| | | const showSpecificationPicker = ref(false); |
| | | const showTaxRatePicker = ref(false); |
| | | const showInvoiceTypePicker = ref(false); |
| | | const pickerSpecificationValue = ref(['']); |
| | | const pickerTaxRateValue = ref(['']); |
| | | const pickerInvoiceTypeValue = ref(['']); |
| | | const currentProductIndex = ref(0); |
| | | |
| | | // 选项数据 |
| | | const productOptions = ref([]); |
| | | const selectedCategoryNode = ref(null); |
| | | const defaultProps = ref({ |
| | | children: 'children', |
| | | label: 'label', |
| | | nodeKey: 'id' |
| | | }); |
| | | |
| | | const modelOptions = ref([]); |
| | | // 防止循环计算的标志 |
| | | const taxRateOptions = ref([ |
| | | { text: '1', value: '1' }, |
| | | { text: '6', value: '6' }, |
| | | { text: '13', value: '13' }, |
| | | ]); |
| | | |
| | | const invoiceTypeOptions = ref([ |
| | | { text: '增普票', value: '增普票' }, |
| | | { text: '增专票', value: '增专票' }, |
| | | ]); |
| | | |
| | | const addProduct = () => { |
| | | if (productData.value === null) { |
| | | productData.value = [] |
| | | } |
| | | productData.value.push({ |
| | | productCategory: '', |
| | | specificationModel: '', |
| | | productModelId: '', |
| | | unit: '', |
| | | taxRate: '', |
| | | taxInclusiveUnitPrice: '', |
| | | quantity: '', |
| | | taxInclusiveTotalPrice: '', |
| | | taxExclusiveTotalPrice: '', |
| | | invoiceType: '' |
| | | const userStore = useUserStore(); |
| | | const form = ref({ |
| | | id: "", |
| | | salesContractNo: "", |
| | | purchaseContractNumber: "", |
| | | supplierId: "", |
| | | supplierName: "", |
| | | projectName: "", |
| | | paymentMethod: "", |
| | | recorderId: "", |
| | | recorderName: "", |
| | | entryDate: "", |
| | | approveUserIds: "", |
| | | executionDate: "", |
| | | }); |
| | | }; |
| | | const onConfirm = ({ selectedValues, selectedOptions }) => { |
| | | form.value.salesContractNo = selectedOptions[0]?.text; |
| | | form.value.salesLedgerId = selectedOptions[0]?.value; |
| | | pickerValue.value = [selectedValues[0]]; |
| | | showPicker.value = false; |
| | | }; |
| | | const onCustomerConfirm = ({ selectedValues, selectedOptions }) => { |
| | | form.value.supplierName = selectedOptions[0]?.text; |
| | | form.value.supplierId = selectedOptions[0]?.value; |
| | | pickerCustomerValue.value = [selectedValues[0]]; |
| | | showCustomerPicker.value = false; |
| | | }; |
| | | const removeProduct = (idx) => { |
| | | productData.value.splice(idx, 1); |
| | | }; |
| | | const showTimePicker = ref(false); |
| | | const showPicker = ref(false); |
| | | const showCustomerPicker = ref(false); |
| | | const salesContractList = ref([]); |
| | | const supplierList = ref([]); |
| | | const productData = ref([]); |
| | | const currentDate = ref(Date.now()); |
| | | // 计算销售合同号选择列表 |
| | | const salesContractActionList = computed(() => { |
| | | return salesContractList.value.map(item => ({ |
| | | name: item.text, |
| | | value: item.value, |
| | | })); |
| | | }); |
| | | |
| | | // 显示选择器 |
| | | const openCategoryPicker = (idx) => { |
| | | currentProductIndex.value = idx; |
| | | showCategoryPicker.value = true; |
| | | }; |
| | | // 计算供应商选择列表 |
| | | const supplierActionList = computed(() => { |
| | | return supplierList.value.map(item => ({ |
| | | name: item.text, |
| | | value: item.value, |
| | | })); |
| | | }); |
| | | |
| | | const openSpecificationPicker = (idx) => { |
| | | currentProductIndex.value = idx; |
| | | showSpecificationPicker.value = true; |
| | | }; |
| | | // 选择器相关变量 |
| | | const showCategoryPicker = ref(false); |
| | | const showSpecificationPicker = ref(false); |
| | | const showTaxRatePicker = ref(false); |
| | | const showInvoiceTypePicker = ref(false); |
| | | const currentProductIndex = ref(0); |
| | | |
| | | const openTaxRatePicker = (idx) => { |
| | | currentProductIndex.value = idx; |
| | | showTaxRatePicker.value = true; |
| | | }; |
| | | // 选项数据 |
| | | const productOptions = ref([]); |
| | | const selectedCategoryNode = ref(null); |
| | | const defaultProps = ref({ |
| | | children: "children", |
| | | label: "label", |
| | | nodeKey: "id", |
| | | }); |
| | | |
| | | const openInvoiceTypePicker = (idx) => { |
| | | currentProductIndex.value = idx; |
| | | showInvoiceTypePicker.value = true; |
| | | }; |
| | | const modelOptions = ref([]); |
| | | const taxRateOptions = ref([ |
| | | { text: "1", value: "1" }, |
| | | { text: "6", value: "6" }, |
| | | { text: "13", value: "13" }, |
| | | ]); |
| | | |
| | | // 选择器确认事件 |
| | | const onCategoryConfirm = (node) => { |
| | | // 获取选中的节点信息 |
| | | console.log('selected node---', node); |
| | | // 存储选中的节点,用于确认时获取数据 |
| | | selectedCategoryNode.value = node; |
| | | }; |
| | | const invoiceTypeOptions = ref([ |
| | | { text: "增普票", value: "增普票" }, |
| | | { text: "增专票", value: "增专票" }, |
| | | ]); |
| | | |
| | | // 确认产品大类选择 |
| | | const confirmCategorySelection = () => { |
| | | if (selectedCategoryNode.value) { |
| | | // 设置选中的产品大类 |
| | | productData.value[currentProductIndex.value].productCategory = selectedCategoryNode.value.label; |
| | | const id = selectedCategoryNode.value.id |
| | | // 重置选中的节点 |
| | | selectedCategoryNode.value = null; |
| | | productData.value[currentProductIndex.value].specificationModel = '' |
| | | productData.value[currentProductIndex.value].productModelId = '' |
| | | productData.value[currentProductIndex.value].pickerSpecificationValue = [''] |
| | | getModels(id) |
| | | } |
| | | showCategoryPicker.value = false; |
| | | }; |
| | | // 获取规格型号 |
| | | const getModels = (value) => { |
| | | modelList({ id: value }).then((res) => { |
| | | modelOptions.value = res.map(user => ({ |
| | | text: user.model, |
| | | value: user.id, |
| | | unit: user.unit, |
| | | })); |
| | | }); |
| | | }; |
| | | // 选择规格型号 |
| | | const onSpecificationConfirm = ({ selectedValues, selectedOptions }) => { |
| | | productData.value[currentProductIndex.value].specificationModel = selectedOptions[0]?.text; |
| | | productData.value[currentProductIndex.value].productModelId = selectedOptions[0]?.value; |
| | | productData.value[currentProductIndex.value].unit = selectedOptions[0]?.unit; |
| | | pickerSpecificationValue.value = [selectedValues[0]]; |
| | | showSpecificationPicker.value = false; |
| | | }; |
| | | // 选择税率 |
| | | const onTaxRateConfirm = ({ selectedValues, selectedOptions }) => { |
| | | productData.value[currentProductIndex.value].taxRate = selectedOptions[0]?.value; |
| | | pickerTaxRateValue.value = [selectedValues[0]]; |
| | | showTaxRatePicker.value = false; |
| | | // if (isCalculating.value) return; |
| | | const inclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice); |
| | | const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate); |
| | | if (!inclusiveTotalPrice || !taxRate) { |
| | | return; |
| | | } |
| | | // isCalculating.value = true; |
| | | // 计算不含税总价 |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | inclusiveTotalPrice, |
| | | taxRate |
| | | ); |
| | | // isCalculating.value = false; |
| | | }; |
| | | // 计算规格型号选择列表 |
| | | const specificationActionList = computed(() => { |
| | | return modelOptions.value.map(model => ({ |
| | | name: model.text, |
| | | value: model.value, |
| | | unit: model.unit, |
| | | })); |
| | | }); |
| | | |
| | | const onInvoiceTypeConfirm = ({ selectedValues, selectedOptions }) => { |
| | | productData.value[currentProductIndex.value].invoiceType = selectedOptions[0]?.text; |
| | | pickerInvoiceTypeValue.value = [selectedValues[0]]; |
| | | showInvoiceTypePicker.value = false; |
| | | }; |
| | | // 计算税率选择列表 |
| | | const taxRateActionList = computed(() => { |
| | | return taxRateOptions.value.map(rate => ({ |
| | | name: rate.text, |
| | | value: rate.value, |
| | | })); |
| | | }); |
| | | |
| | | // 格式化函数 - 固定两位小数 |
| | | const formatTaxPrice = (idx) => { |
| | | if (productData.value[idx].taxInclusiveUnitPrice) { |
| | | const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2); |
| | | // 计算发票类型选择列表 |
| | | const invoiceTypeActionList = computed(() => { |
| | | return invoiceTypeOptions.value.map(type => ({ |
| | | name: type.text, |
| | | value: type.value, |
| | | })); |
| | | }); |
| | | |
| | | // 表单校验规则 |
| | | const rules = { |
| | | salesContractNo: [ |
| | | { required: true, message: "请选择销售合同号", trigger: "blur" }, |
| | | ], |
| | | supplierName: [ |
| | | { required: true, message: "请选择供应商名称", trigger: "blur" }, |
| | | ], |
| | | projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }], |
| | | executionDate: [ |
| | | { required: true, message: "请选择签订日期", trigger: "blur" }, |
| | | ], |
| | | }; |
| | | |
| | | // 产品信息校验规则 |
| | | const productRules = { |
| | | productCategory: [ |
| | | { required: true, message: "请选择产品大类", trigger: "blur" }, |
| | | ], |
| | | specificationModel: [ |
| | | { required: true, message: "请选择规格型号", trigger: "blur" }, |
| | | ], |
| | | unit: [{ required: true, message: "请输入单位", trigger: "blur" }], |
| | | taxRate: [{ required: true, message: "请选择税率", trigger: "blur" }], |
| | | taxInclusiveUnitPrice: [ |
| | | { required: true, message: "请输入含税单价", trigger: "blur" }, |
| | | { type: "number", min: 0, message: "含税单价必须大于0", trigger: "blur" }, |
| | | ], |
| | | quantity: [ |
| | | { required: true, message: "请输入数量", trigger: "blur" }, |
| | | { type: "number", min: 0, message: "数量必须大于0", trigger: "blur" }, |
| | | ], |
| | | taxInclusiveTotalPrice: [ |
| | | { required: true, message: "请输入含税总价", trigger: "blur" }, |
| | | { type: "number", min: 0, message: "含税总价必须大于0", trigger: "blur" }, |
| | | ], |
| | | taxExclusiveTotalPrice: [ |
| | | { required: true, message: "请输入不含税总价", trigger: "blur" }, |
| | | { type: "number", min: 0, message: "不含税总价必须大于0", trigger: "blur" }, |
| | | ], |
| | | invoiceType: [{ required: true, message: "请选择发票类型", trigger: "blur" }], |
| | | |
| | | warnNum: [ |
| | | { required: true, message: "请输入库存预警数量", trigger: "blur" }, |
| | | { |
| | | type: "number", |
| | | min: 0, |
| | | message: "库存预警数量必须大于0", |
| | | trigger: "blur", |
| | | }, |
| | | ], |
| | | }; |
| | | |
| | | const addProduct = () => { |
| | | if (productData.value === null) { |
| | | productData.value = []; |
| | | } |
| | | } |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: '请先选择税率', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); |
| | | const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice); |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | // 计算含税总价 |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | // 数量输入框失焦 |
| | | const formatAmount = (idx) => { |
| | | if (productData.value[idx].quantity) { |
| | | const value = parseFloat(productData.value[idx].quantity); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].quantity = value.toFixed(2); |
| | | productData.value.push({ |
| | | productCategory: "", |
| | | specificationModel: "", |
| | | productModelId: "", |
| | | unit: "", |
| | | taxRate: "", |
| | | taxInclusiveUnitPrice: "", |
| | | quantity: "", |
| | | taxInclusiveTotalPrice: "", |
| | | taxExclusiveTotalPrice: "", |
| | | invoiceType: "", |
| | | isChecked: false, |
| | | warnNum: "", |
| | | }); |
| | | }; |
| | | |
| | | // 销售合同号选择事件 |
| | | const onSalesmanSelect = item => { |
| | | form.value.salesContractNo = item.name; |
| | | // 查找对应的id |
| | | const selectedItem = salesContractList.value.find( |
| | | contract => contract.text === item.name |
| | | ); |
| | | if (selectedItem) { |
| | | form.value.salesLedgerId = selectedItem.value; |
| | | } |
| | | } |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: '请先选择税率', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); |
| | | const unitPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveUnitPrice); |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | // 计算含税总价 |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = (unitPrice * quantity).toFixed(2); |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | // 含税总价失焦,根据含税总价计算含税单价和数量 |
| | | const formatTaxTotal = (idx) => { |
| | | if (productData.value[idx].taxInclusiveTotalPrice) { |
| | | const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2); |
| | | showPicker.value = false; |
| | | }; |
| | | |
| | | // 供应商选择事件 |
| | | const onCustomerSelect = item => { |
| | | form.value.supplierName = item.name; |
| | | // 查找对应的id |
| | | const selectedItem = supplierList.value.find( |
| | | supplier => supplier.text === item.name |
| | | ); |
| | | if (selectedItem) { |
| | | form.value.supplierId = selectedItem.value; |
| | | } |
| | | } |
| | | const totalPrice = parseFloat(productData.value[currentProductIndex.value].taxInclusiveTotalPrice); |
| | | const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); |
| | | |
| | | if (!totalPrice || !quantity || quantity <= 0) { |
| | | return; |
| | | } |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (totalPrice / quantity).toFixed(2); |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | totalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | // 不含税总价失焦, 根据不含税总价计算含税单价和数量 |
| | | const formatNoTaxTotal = (idx) => { |
| | | if (productData.value[idx].taxExclusiveTotalPrice) { |
| | | const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2); |
| | | showCustomerPicker.value = false; |
| | | }; |
| | | |
| | | const removeProduct = idx => { |
| | | productData.value.splice(idx, 1); |
| | | }; |
| | | |
| | | // 显示选择器 |
| | | const openCategoryPicker = idx => { |
| | | currentProductIndex.value = idx; |
| | | showCategoryPicker.value = true; |
| | | }; |
| | | |
| | | const openSpecificationPicker = idx => { |
| | | currentProductIndex.value = idx; |
| | | showSpecificationPicker.value = true; |
| | | }; |
| | | |
| | | const openTaxRatePicker = idx => { |
| | | currentProductIndex.value = idx; |
| | | showTaxRatePicker.value = true; |
| | | }; |
| | | |
| | | const openInvoiceTypePicker = idx => { |
| | | currentProductIndex.value = idx; |
| | | showInvoiceTypePicker.value = true; |
| | | }; |
| | | |
| | | // 选择器确认事件 |
| | | const onCategoryConfirm = node => { |
| | | // 获取选中的节点信息 |
| | | console.log("selected node---", node); |
| | | // 存储选中的节点,用于确认时获取数据 |
| | | selectedCategoryNode.value = node; |
| | | }; |
| | | |
| | | // 确认产品大类选择 |
| | | const confirmCategorySelection = () => { |
| | | if (selectedCategoryNode.value) { |
| | | // 设置选中的产品大类 |
| | | productData.value[currentProductIndex.value].productCategory = |
| | | selectedCategoryNode.value.label; |
| | | const id = selectedCategoryNode.value.id; |
| | | // 重置选中的节点 |
| | | selectedCategoryNode.value = null; |
| | | productData.value[currentProductIndex.value].specificationModel = ""; |
| | | productData.value[currentProductIndex.value].productModelId = ""; |
| | | getModels(id); |
| | | } |
| | | } |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: '请先选择税率', |
| | | icon: 'none' |
| | | }); |
| | | return; |
| | | } |
| | | const exclusiveTotalPrice = parseFloat(productData.value[currentProductIndex.value].taxExclusiveTotalPrice); |
| | | const quantity = parseFloat(productData.value[currentProductIndex.value].quantity); |
| | | const taxRate = parseFloat(productData.value[currentProductIndex.value].taxRate); |
| | | if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) { |
| | | return; |
| | | } |
| | | // 先计算含税总价 = 不含税总价 / (1 - 税率/100) |
| | | const taxRateDecimal = taxRate / 100; |
| | | const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = inclusiveTotalPrice.toFixed(2); |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice = (inclusiveTotalPrice / quantity).toFixed(2); |
| | | }; |
| | | const goBack = () => { |
| | | // 清理本地存储的数据 |
| | | uni.removeStorageSync('operationType'); |
| | | uni.removeStorageSync('editData'); |
| | | uni.navigateBack(); |
| | | }; |
| | | const onSubmit = () => { |
| | | if (productData.value !== null && productData.value.length > 0) { |
| | | form.value.productData = JSON.parse(JSON.stringify(productData.value)); |
| | | } else { |
| | | uni.showToast({ |
| | | title: '请添加产品信息', |
| | | icon: 'none' |
| | | }); |
| | | return |
| | | } |
| | | form.value.type = 2; |
| | | addOrEditPurchase(form.value).then((res) => { |
| | | uni.showToast({ |
| | | title: '提交成功', |
| | | icon: 'success', |
| | | }); |
| | | goBack(); |
| | | }); |
| | | }; |
| | | const setUserInfo = () => { |
| | | form.value.recorderId = userStore.id; |
| | | form.value.recorderName = userStore.nickName; |
| | | // 设置当天日期 |
| | | 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') |
| | | form.value.entryDate = `${year}-${month}-${day}` |
| | | pickerDateValue.value = [year.toString(), month.toString(), day.toString()] |
| | | } |
| | | // 填充表单数据(编辑模式) |
| | | const fillFormData = () => { |
| | | if (!editData.value) return; |
| | | getPurchaseById({ id: editData.value.id, type: 2 }).then((res) => { |
| | | productData.value = res.productData; |
| | | }); |
| | | console.log(editData.value) |
| | | // 填充基本信息 |
| | | form.value.salesContractNo = editData.value.salesContractNo || ''; |
| | | form.value.supplierName = editData.value.supplierName || ''; |
| | | form.value.projectName = editData.value.projectName || ''; |
| | | form.value.executionDate = editData.value.executionDate || ''; |
| | | form.value.paymentMethod = editData.value.paymentMethod || ''; |
| | | form.value.salesLedgerId = editData.value.salesLedgerId || ''; |
| | | form.value.recorderId = editData.value.recorderId || ''; |
| | | form.value.recorderName = editData.value.recorderName || ''; |
| | | form.value.entryDate = editData.value.entryDate || ''; |
| | | form.value.id = editData.value.id || ''; |
| | | form.value.supplierId = editData.value.supplierId || ''; |
| | | |
| | | // 设置销售合同号选择器的值 |
| | | if (editData.value.salesContractNo) { |
| | | const salesmanIndex = salesContractList.value.findIndex(user => user.text === editData.value.salesContractNo); |
| | | if (salesmanIndex !== -1) { |
| | | pickerValue.value = [salesContractList.value[salesmanIndex].value]; |
| | | showCategoryPicker.value = false; |
| | | }; |
| | | |
| | | // 获取规格型号 |
| | | const getModels = value => { |
| | | modelList({ id: value }).then(res => { |
| | | modelOptions.value = res.map(user => ({ |
| | | text: user.model, |
| | | value: user.id, |
| | | unit: user.unit, |
| | | })); |
| | | }); |
| | | }; |
| | | |
| | | // 规格型号选择事件 |
| | | const onSpecificationSelect = item => { |
| | | productData.value[currentProductIndex.value].specificationModel = item.name; |
| | | productData.value[currentProductIndex.value].productModelId = item.value; |
| | | productData.value[currentProductIndex.value].unit = item.unit; |
| | | showSpecificationPicker.value = false; |
| | | }; |
| | | |
| | | // 税率选择事件 |
| | | const onTaxRateSelect = item => { |
| | | productData.value[currentProductIndex.value].taxRate = item.value; |
| | | showTaxRatePicker.value = false; |
| | | // 重新计算不含税总价 |
| | | const inclusiveTotalPrice = parseFloat( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice |
| | | ); |
| | | const taxRate = parseFloat(item.value); |
| | | if (inclusiveTotalPrice && taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice(inclusiveTotalPrice, taxRate); |
| | | } |
| | | } |
| | | |
| | | // 设置供应商选择器的值 |
| | | if (editData.value.supplierName) { |
| | | const customerIndex = supplierList.value.findIndex(customer => customer.text === editData.value.supplierName); |
| | | if (customerIndex !== -1) { |
| | | pickerCustomerValue.value = [supplierList.value[customerIndex].value] |
| | | }; |
| | | |
| | | // 发票类型选择事件 |
| | | const onInvoiceTypeSelect = item => { |
| | | productData.value[currentProductIndex.value].invoiceType = item.name; |
| | | showInvoiceTypePicker.value = false; |
| | | }; |
| | | |
| | | // 格式化函数 - 固定两位小数 |
| | | const formatTaxPrice = idx => { |
| | | if (productData.value[idx].taxInclusiveUnitPrice) { |
| | | const value = parseFloat(productData.value[idx].taxInclusiveUnitPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxInclusiveUnitPrice = value.toFixed(2); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 设置日期选择器的值 |
| | | if (editData.value.executionDate) { |
| | | pickerDateValue.value = editData.value.executionDate.split('-').map(num => parseInt(num, 10)) |
| | | console.log(pickerDateValue.value) |
| | | } |
| | | }; |
| | | const getSalesNoList = () => { |
| | | getSalesNo().then((res) => { |
| | | // 将用户数据组装成 picker 需要的格式 |
| | | salesContractList.value = res.map(user => ({ |
| | | text: user.salesContractNo, |
| | | value: user.id |
| | | })); |
| | | }) |
| | | } |
| | | const getOptionsLIst = () => { |
| | | getOptions().then((res) => { |
| | | // 将用户数据组装成 picker 需要的格式 |
| | | supplierList.value = res.data.map(item => ({ |
| | | text: item.supplierName, |
| | | value: item.id |
| | | })); |
| | | }) |
| | | } |
| | | const convertIdToValue = (data) => { |
| | | // 如果传入的不是数组,则返回空数组 |
| | | if (!Array.isArray(data)) { |
| | | return []; |
| | | } |
| | | // 递归映射函数 |
| | | return data.map(item => { |
| | | // 创建新对象,映射字段 |
| | | const mappedItem = { |
| | | label: item.label, // 关键:将 label 映射为 text |
| | | id: item.id, // 保留 id |
| | | }; |
| | | // 如果存在 children 数组,则递归处理 |
| | | if (item.children && Array.isArray(item.children) && item.children.length > 0) { |
| | | mappedItem.children = convertIdToValue(item.children); |
| | | } |
| | | return mappedItem; |
| | | }); |
| | | }; |
| | | // 获取产品大类tree数据 |
| | | const getProductOptions = () => { |
| | | productTreeList().then((res) => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | onMounted(() => { |
| | | // 获取页面参数 |
| | | operationType.value = uni.getStorageSync('operationType') || ''; |
| | | |
| | | // 获取销售合同号列表 |
| | | getSalesNoList() |
| | | // 获取供应商列表 |
| | | getOptionsLIst() |
| | | // 获取产品大类tree数据 |
| | | getProductOptions() |
| | | // 赋值默认信息 |
| | | if (operationType.value === 'add') { |
| | | setUserInfo() |
| | | createPurchaseNo().then((res) => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | } |
| | | |
| | | // 获取编辑数据并填充表单 |
| | | const editDataStr = uni.getStorageSync('editData'); |
| | | if (editDataStr) { |
| | | try { |
| | | editData.value = JSON.parse(editDataStr); |
| | | // 如果是编辑模式,等待数据加载完成后填充表单数据 |
| | | if (operationType.value !== 'add' && editData.value) { |
| | | // 使用 nextTick 确保数据加载完成后再填充 |
| | | setTimeout(() => { |
| | | fillFormData(); |
| | | }, 100); |
| | | } |
| | | } catch (error) { |
| | | console.error('解析编辑数据失败:', error); |
| | | } |
| | | } |
| | | }); |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: "请先选择税率", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | const quantity = parseFloat( |
| | | productData.value[currentProductIndex.value].quantity |
| | | ); |
| | | const unitPrice = parseFloat( |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice |
| | | ); |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | // 计算含税总价 |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = ( |
| | | unitPrice * quantity |
| | | ).toFixed(2); |
| | | |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | // 数量输入框失焦 |
| | | const formatAmount = idx => { |
| | | if (productData.value[idx].quantity) { |
| | | const value = parseFloat(productData.value[idx].quantity); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].quantity = value.toFixed(2); |
| | | } |
| | | } |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: "请先选择税率", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | const quantity = parseFloat( |
| | | productData.value[currentProductIndex.value].quantity |
| | | ); |
| | | const unitPrice = parseFloat( |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice |
| | | ); |
| | | |
| | | if (!quantity || quantity <= 0 || !unitPrice) { |
| | | return; |
| | | } |
| | | // 计算含税总价 |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = ( |
| | | unitPrice * quantity |
| | | ).toFixed(2); |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | // 含税总价失焦,根据含税总价计算含税单价和数量 |
| | | const formatTaxTotal = idx => { |
| | | if (productData.value[idx].taxInclusiveTotalPrice) { |
| | | const value = parseFloat(productData.value[idx].taxInclusiveTotalPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxInclusiveTotalPrice = value.toFixed(2); |
| | | } |
| | | } |
| | | const totalPrice = parseFloat( |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice |
| | | ); |
| | | const quantity = parseFloat( |
| | | productData.value[currentProductIndex.value].quantity |
| | | ); |
| | | |
| | | if (!totalPrice || !quantity || quantity <= 0) { |
| | | return; |
| | | } |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice = ( |
| | | totalPrice / quantity |
| | | ).toFixed(2); |
| | | // 如果有税率,计算不含税总价 |
| | | if (productData.value[currentProductIndex.value].taxRate) { |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice = |
| | | calculateTaxExclusiveTotalPrice( |
| | | totalPrice, |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | // 不含税总价失焦, 根据不含税总价计算含税单价和数量 |
| | | const formatNoTaxTotal = idx => { |
| | | if (productData.value[idx].taxExclusiveTotalPrice) { |
| | | const value = parseFloat(productData.value[idx].taxExclusiveTotalPrice); |
| | | if (!isNaN(value)) { |
| | | productData.value[idx].taxExclusiveTotalPrice = value.toFixed(2); |
| | | } |
| | | } |
| | | if (!productData.value[currentProductIndex.value].taxRate) { |
| | | uni.showToast({ |
| | | title: "请先选择税率", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | const exclusiveTotalPrice = parseFloat( |
| | | productData.value[currentProductIndex.value].taxExclusiveTotalPrice |
| | | ); |
| | | const quantity = parseFloat( |
| | | productData.value[currentProductIndex.value].quantity |
| | | ); |
| | | const taxRate = parseFloat( |
| | | productData.value[currentProductIndex.value].taxRate |
| | | ); |
| | | if (!exclusiveTotalPrice || !quantity || quantity <= 0 || !taxRate) { |
| | | return; |
| | | } |
| | | // 先计算含税总价 = 不含税总价 / (1 - 税率/100) |
| | | const taxRateDecimal = taxRate / 100; |
| | | const inclusiveTotalPrice = exclusiveTotalPrice / (1 - taxRateDecimal); |
| | | productData.value[currentProductIndex.value].taxInclusiveTotalPrice = |
| | | inclusiveTotalPrice.toFixed(2); |
| | | // 计算含税单价 = 含税总价 / 数量 |
| | | productData.value[currentProductIndex.value].taxInclusiveUnitPrice = ( |
| | | inclusiveTotalPrice / quantity |
| | | ).toFixed(2); |
| | | }; |
| | | |
| | | const goBack = () => { |
| | | // 清理本地存储的数据 |
| | | uni.removeStorageSync("operationType"); |
| | | uni.removeStorageSync("editData"); |
| | | uni.navigateBack(); |
| | | }; |
| | | |
| | | const onSubmit = () => { |
| | | const hasEmptyApprover = approverNodes.value.some(node => !node.userId); |
| | | if (hasEmptyApprover) { |
| | | uni.showToast({ |
| | | title: "请为所有审批节点选择审批人!", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | const approveUserIds = approverNodes.value.map(node => node.userId).join(","); |
| | | |
| | | if (productData.value !== null && productData.value.length > 0) { |
| | | form.value.productData = JSON.parse(JSON.stringify(productData.value)); |
| | | } else { |
| | | uni.showToast({ |
| | | title: "请添加产品信息", |
| | | icon: "none", |
| | | }); |
| | | return; |
| | | } |
| | | // 如果salesLedgerId为空,则不传递salesContractNo |
| | | if (!form.value.salesLedgerId) { |
| | | form.value.salesContractNo = ""; |
| | | } |
| | | if (operationType.value == "add") { |
| | | delete form.value.id; |
| | | } |
| | | form.value.approveUserIds = approveUserIds; |
| | | form.value.type = 2; |
| | | addOrEditPurchase(form.value).then(res => { |
| | | uni.showToast({ |
| | | title: "提交成功", |
| | | icon: "success", |
| | | }); |
| | | goBack(); |
| | | }); |
| | | }; |
| | | |
| | | const setUserInfo = () => { |
| | | form.value.recorderId = userStore.id; |
| | | form.value.recorderName = userStore.nickName; |
| | | // 设置当天日期 |
| | | 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"); |
| | | form.value.entryDate = `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | // 确认日期选择 |
| | | const onDateConfirm = e => { |
| | | form.value.executionDate = formatDateToYMD(e.value); |
| | | currentDate.value = e.value; |
| | | showTimePicker.value = false; |
| | | }; |
| | | |
| | | // 填充表单数据(编辑模式) |
| | | const fillFormData = () => { |
| | | if (!editData.value) return; |
| | | getPurchaseById({ id: editData.value.id, type: 2 }).then(res => { |
| | | productData.value = res.productData; |
| | | if (res && res.approveUserIds) { |
| | | const userIds = res.approveUserIds.split(","); |
| | | approverNodes.value = userIds.map((userId, idx) => { |
| | | const userIdNum = parseInt(userId.trim()); |
| | | // 从userList中找到对应的用户信息 |
| | | console.log(userList.value, "userList.value"); |
| | | 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; |
| | | } |
| | | }); |
| | | console.log(editData.value); |
| | | // 填充基本信息 |
| | | form.value.purchaseContractNumber = |
| | | editData.value.purchaseContractNumber || ""; |
| | | form.value.salesContractNo = editData.value.salesContractNo || ""; |
| | | form.value.supplierName = editData.value.supplierName || ""; |
| | | form.value.projectName = editData.value.projectName || ""; |
| | | form.value.paymentMethod = editData.value.paymentMethod || ""; |
| | | form.value.salesLedgerId = editData.value.salesLedgerId || ""; |
| | | form.value.recorderId = editData.value.recorderId || ""; |
| | | form.value.recorderName = editData.value.recorderName || ""; |
| | | form.value.entryDate = editData.value.entryDate || ""; |
| | | form.value.id = editData.value.id || ""; |
| | | form.value.supplierId = editData.value.supplierId || ""; |
| | | form.value.executionDate = editData.value.executionDate || ""; |
| | | }; |
| | | |
| | | const getSalesNoList = () => { |
| | | getSalesNo().then(res => { |
| | | // 将用户数据组装成 picker 需要的格式 |
| | | salesContractList.value = res.map(user => ({ |
| | | text: user.salesContractNo, |
| | | value: user.id, |
| | | })); |
| | | }); |
| | | }; |
| | | |
| | | const getOptionsLIst = () => { |
| | | getOptions().then(res => { |
| | | // 将用户数据组装成 picker 需要的格式 |
| | | supplierList.value = res.data.map(item => ({ |
| | | text: item.supplierName, |
| | | value: item.id, |
| | | })); |
| | | }); |
| | | }; |
| | | |
| | | const convertIdToValue = data => { |
| | | // 如果传入的不是数组,则返回空数组 |
| | | if (!Array.isArray(data)) { |
| | | return []; |
| | | } |
| | | // 递归映射函数 |
| | | return data.map(item => { |
| | | // 创建新对象,映射字段 |
| | | const mappedItem = { |
| | | label: item.label, // 关键:将 label 映射为 text |
| | | id: item.id, // 保留 id |
| | | }; |
| | | // 如果存在 children 数组,则递归处理 |
| | | if ( |
| | | item.children && |
| | | Array.isArray(item.children) && |
| | | item.children.length > 0 |
| | | ) { |
| | | mappedItem.children = convertIdToValue(item.children); |
| | | } |
| | | return mappedItem; |
| | | }); |
| | | }; |
| | | |
| | | // 获取产品大类tree数据 |
| | | const getProductOptions = () => { |
| | | productTreeList().then(res => { |
| | | productOptions.value = convertIdToValue(res); |
| | | }); |
| | | }; |
| | | const approverNodes = ref([]); |
| | | let nextApproverId = 2; |
| | | const userList = ref([]); |
| | | onMounted(() => { |
| | | // 获取页面参数 |
| | | operationType.value = uni.getStorageSync("operationType") || ""; |
| | | userListNoPageByTenantId().then(res => { |
| | | userList.value = res.data; |
| | | }); |
| | | // 获取销售合同号列表 |
| | | getSalesNoList(); |
| | | // 获取供应商列表 |
| | | getOptionsLIst(); |
| | | // 获取产品大类tree数据 |
| | | getProductOptions(); |
| | | // 赋值默认信息 |
| | | if (operationType.value === "add") { |
| | | setUserInfo(); |
| | | createPurchaseNo().then(res => { |
| | | form.value.purchaseContractNumber = res.data; |
| | | }); |
| | | } |
| | | |
| | | // 监听联系人选择事件 |
| | | uni.$on("selectContact", handleSelectContact); |
| | | |
| | | // 获取编辑数据并填充表单 |
| | | const editDataStr = uni.getStorageSync("editData"); |
| | | if (editDataStr) { |
| | | try { |
| | | editData.value = JSON.parse(editDataStr); |
| | | // 如果是编辑模式,等待数据加载完成后填充表单数据 |
| | | if (operationType.value !== "add" && editData.value) { |
| | | // 使用 nextTick 确保数据加载完成后再填充 |
| | | setTimeout(() => { |
| | | fillFormData(); |
| | | }, 100); |
| | | } |
| | | } catch (error) { |
| | | console.error("解析编辑数据失败:", error); |
| | | } |
| | | } else { |
| | | approverNodes.value = [{ id: 1, userId: null }]; |
| | | } |
| | | }); |
| | | // 处理联系人选择结果 |
| | | const handleSelectContact = data => { |
| | | const { stepIndex, contact } = data; |
| | | // 将选中的联系人设置为对应审批步骤的审批人 |
| | | console.log(contact); |
| | | console.log(stepIndex, "stepIndex"); |
| | | console.log(approverNodes.value[stepIndex], "审批人"); |
| | | 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 }); |
| | | console.log(approverNodes.value, "approverNodes.value"); |
| | | }; |
| | | |
| | | 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", |
| | | }); |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .account-detail { |
| | | min-height: 100vh; |
| | | background: #f8f9fa; |
| | | padding-bottom: 5rem; |
| | | } |
| | | .header { |
| | | display: flex; |
| | | align-items: center; |
| | | background: #fff; |
| | | padding: 1rem 1.25rem; |
| | | border-bottom: 0.0625rem solid #f0f0f0; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 100; |
| | | /* 兼容 iOS 刘海/灵动岛安全区 */ |
| | | padding-top: env(safe-area-inset-top); |
| | | } |
| | | .title { |
| | | flex: 1; |
| | | text-align: center; |
| | | font-size: 1.125rem; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .form-section { |
| | | margin-top: 1rem; |
| | | } |
| | | .u-form-item { |
| | | height: 3.4rem; |
| | | } |
| | | .u-cell { |
| | | align-items: center; |
| | | } |
| | | .product-section { |
| | | background: #fff; |
| | | margin-top: 1rem; |
| | | padding: 1rem; |
| | | box-shadow: 0 0.125rem 0.5rem rgba(0,0,0,0.04); |
| | | } |
| | | .section-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | margin-bottom: 1rem; |
| | | } |
| | | .section-title { |
| | | font-size: 1rem; |
| | | font-weight: 600; |
| | | color: #333; |
| | | } |
| | | .product-card { |
| | | background: #FFFFFF; |
| | | box-shadow: 0 0 1.25rem 0 rgba(0,57,117,0.08); |
| | | border-radius: 0.5rem 0.5rem 0.5rem 0.5rem; |
| | | padding: 1rem 0.5rem 0 0.5rem; |
| | | position: relative; |
| | | } |
| | | .product-header { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | padding: 0 0.5rem 0.75rem 0.5rem; |
| | | border-bottom: 0.0625rem solid #e8e8e8; |
| | | } |
| | | .product-productCategory { |
| | | margin-left: 0.5rem; |
| | | font-size: 0.875rem; |
| | | font-weight: 500; |
| | | color: #333; |
| | | } |
| | | .info-grid { |
| | | display: grid; |
| | | grid-template-columns: 1fr 1fr; |
| | | gap: 0.75rem; |
| | | margin-bottom: 1rem; |
| | | } |
| | | .info-item { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 0.25rem; |
| | | } |
| | | .info-label { |
| | | font-size: 0.75rem; |
| | | color: #666; |
| | | font-weight: 400; |
| | | } |
| | | .info-value { |
| | | font-size: 0.875rem; |
| | | color: #333; |
| | | font-weight: 500; |
| | | } |
| | | .info-value.highlight { |
| | | color: #2979ff; |
| | | font-weight: 600; |
| | | } |
| | | .product-form { |
| | | margin-bottom: 1rem; |
| | | } |
| | | .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"; |
| | | |
| | | .popup-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 1rem; |
| | | background: #fff; |
| | | position: sticky; |
| | | top: 0; |
| | | z-index: 10; |
| | | } |
| | | .cancelButton { |
| | | color: #969799 |
| | | } |
| | | .confirmButton { |
| | | color: #1989FA |
| | | } |
| | | .u-tree { |
| | | height: 13rem; |
| | | } |
| | | </style> |
| | | .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; |
| | | } |
| | | .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; |
| | | } |
| | | |
| | | // 动画定义 |
| | | @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> |