<template>
|
<view class="account-detail">
|
<!-- 使用通用页面头部组件 -->
|
<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, 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 userStore = useUserStore();
|
const form = ref({
|
id: "",
|
salesContractNo: "",
|
purchaseContractNumber: "",
|
supplierId: "",
|
supplierName: "",
|
projectName: "",
|
paymentMethod: "",
|
recorderId: "",
|
recorderName: "",
|
entryDate: "",
|
approveUserIds: "",
|
executionDate: "",
|
});
|
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 supplierActionList = computed(() => {
|
return supplierList.value.map(item => ({
|
name: item.text,
|
value: item.value,
|
}));
|
});
|
|
// 选择器相关变量
|
const showCategoryPicker = ref(false);
|
const showSpecificationPicker = ref(false);
|
const showTaxRatePicker = ref(false);
|
const showInvoiceTypePicker = ref(false);
|
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 specificationActionList = computed(() => {
|
return modelOptions.value.map(model => ({
|
name: model.text,
|
value: model.value,
|
unit: model.unit,
|
}));
|
});
|
|
// 计算税率选择列表
|
const taxRateActionList = computed(() => {
|
return taxRateOptions.value.map(rate => ({
|
name: rate.text,
|
value: rate.value,
|
}));
|
});
|
|
// 计算发票类型选择列表
|
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 = [];
|
}
|
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;
|
}
|
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;
|
}
|
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);
|
}
|
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);
|
}
|
};
|
|
// 发票类型选择事件
|
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 (!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">
|
@import "@/static/scss/form-common.scss";
|
|
.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>
|