gaoluyang
2026-09-20 e449a5408265e4bd1f6c66f5be28a42efac444ee
src/pages/sales/salesAccount/detail.vue
@@ -41,27 +41,102 @@
        </template>
      </up-form-item>
      <up-form-item label="项目名称"
                    prop="projectName"
                    prop="projectId"
                    required>
        <up-input v-model="form.projectName"
                  placeholder="请输入项目名称" />
        <FormPicker v-model="form.projectId"
                    type="select"
                    title="选择项目"
                    :options="projectOptions"
                    placeholder="请选择项目"
                    @change="onProjectChange" />
      </up-form-item>
      <up-form-item label="签订日期"
                    prop="executionDate"
                    required>
        <up-input v-model="form.executionDate"
                  readonly
                  placeholder="点击选择时间"
                  @click="showDatePicker = true" />
        <template #right>
          <up-icon name="arrow-right"
                   @click="showDatePicker = true"></up-icon>
        </template>
        <FormPicker v-model="form.executionDate"
                    type="date"
                    placeholder="请选择签订日期"
                    @change="onExecutionDateChange" />
      </up-form-item>
      <up-form-item label="年份">
        <up-input :model-value="form.year === '' ? '自动取签订日期' : String(form.year)"
                  disabled />
      </up-form-item>
      <up-form-item label="付款方式"
                    prop="paymentMethod">
        <up-input v-model="form.paymentMethod"
                  placeholder="请输入付款方式" />
        <FormPicker v-model="form.paymentMethod"
                    type="select"
                    title="选择付款方式"
                    :display-text="paymentDisplayText"
                    :options="paymentOptions"
                    placeholder="请选择付款方式" />
      </up-form-item>
      <up-form-item label="交货日期"
                    prop="deliveryDate">
        <FormPicker v-model="form.deliveryDate"
                    type="date"
                    placeholder="请选择交货日期" />
      </up-form-item>
      <up-form-item label="签单单位"
                    prop="signingUnitId">
        <FormPicker v-model="form.signingUnitId"
                    type="select"
                    title="选择签单单位"
                    :options="deptOptions"
                    placeholder="请选择签单单位" />
      </up-form-item>
      <up-form-item label="办事处"
                    prop="officeId">
        <FormPicker v-model="form.officeId"
                    type="select"
                    title="选择办事处"
                    :options="deptOptions"
                    placeholder="请选择办事处" />
      </up-form-item>
      <up-form-item label="组负责人"
                    prop="groupLeaderId">
        <FormPicker v-model="form.groupLeaderId"
                    type="select"
                    title="选择组负责人"
                    :options="userOptions"
                    placeholder="请选择组负责人" />
      </up-form-item>
      <up-form-item label="是否开发票">
        <view class="switch-row">
          <up-switch v-model="form.isInvoice"
                     :activeValue="1"
                     :inactiveValue="0" />
        </view>
      </up-form-item>
      <up-form-item label="是否中标">
        <view class="switch-row">
          <up-switch v-model="form.isBid"
                     :activeValue="1"
                     :inactiveValue="0" />
        </view>
      </up-form-item>
      <up-form-item v-if="form.isBid === 1"
                    label="中标价">
        <up-input v-model="form.bidPrice"
                  type="number"
                  placeholder="请输入中标价" />
      </up-form-item>
      <up-form-item v-else
                    label="未中标原因">
        <up-input v-model="form.unbidReason"
                  placeholder="请输入未中标原因" />
      </up-form-item>
      <up-form-item label="合同原件约定回传日期">
        <FormPicker v-model="form.contractOriginalReturnDate"
                    type="date"
                    placeholder="请选择合同原件约定回传日期" />
      </up-form-item>
      <up-form-item label="备注">
        <up-textarea v-model="form.remarks"
                     placeholder="请输入备注"
                     :maxlength="200"
                     count
                     auto-height />
      </up-form-item>
      <up-form-item label="录入人"
                    prop="entryPersonName">
@@ -81,16 +156,6 @@
                       title="选择业务员"
                       @select="onSalesmanSelect"
                       @close="showPicker = false" />
      <!-- 日期选择 -->
      <up-popup :show="showDatePicker"
                mode="bottom"
                @close="showDatePicker = false">
        <up-datetime-picker :show="true"
                            v-model="pickerDateValue"
                            @confirm="onDateConfirm"
                            @cancel="showDatePicker = false"
                            mode="date" />
      </up-popup>
      <!-- 客户选择 -->
      <up-action-sheet :show="showCustomerPicker"
                       :actions="customerActionList"
@@ -283,6 +348,18 @@
            </up-form-item>
          </view>
        </view>
        <!-- 需求五.4:签约金额 = 含税单价 × 数量,即含税总价,按产品行汇总 -->
        <view v-if="productData.length > 0"
              class="product-summary">
          <view class="summary-row">
            <text class="summary-label">签约金额合计(元)</text>
            <text class="summary-value highlight">{{ productTotals.taxInclusive }}</text>
          </view>
          <view class="summary-row">
            <text class="summary-label">不含税总价合计(元)</text>
            <text class="summary-value">{{ productTotals.taxExclusive }}</text>
          </view>
        </view>
      </view>
    </up-form>
    <!-- 使用公共底部按钮组件 -->
@@ -297,7 +374,8 @@
<script setup>
  import { onMounted, ref, computed } from "vue";
  import { userListNoPage } from "@/api/system/user";
  import { formatDateToYMD } from "@/utils/ruoyi";
  import { userListNoPageByTenantId } from "@/api/system/user";
  import { getDept } from "@/api/collaborativeApproval/approvalProcess";
  import {
    addOrUpdateSalesLedger,
    customerList,
@@ -305,10 +383,33 @@
    modelList,
    productTreeList,
  } from "@/api/salesManagement/salesLedger";
  import { getProject } from "@/api/projectManagement/project";
  import useUserStore from "@/store/modules/user";
  import { calculateTaxExclusiveTotalPrice } from "@/utils/summarizeTable";
  import { useDict } from "@/utils/dict";
  import { loadProjectOptions } from "@/utils/projectOptions";
  import { takePrefillProject } from "@/utils/prefill";
  import FormPicker from "@/components/FormPicker.vue";
  import PageHeader from "@/components/PageHeader.vue";
  import FooterButtons from "@/components/FooterButtons.vue";
  const { checkout_payment } = useDict("checkout_payment");
  const paymentOptions = computed(() =>
    (checkout_payment.value || []).map(item => ({
      label: item.label,
      value: item.value,
    }))
  );
  // 改动前付款方式是自由文本,老单据的值可能不在字典里,此时回显原值而不是空白
  const paymentDisplayText = computed(() => {
    const value = form.value.paymentMethod;
    if (!value) return "";
    const matched = paymentOptions.value.find(
      item => String(item.value) === String(value)
    );
    return matched ? matched.label : String(value);
  });
  // 获取页面参数
  const operationType = ref("");
@@ -319,47 +420,47 @@
  const form = ref({
    id: "",
    salesContractNo: "",
    autoGenerateContractNo: true,
    customerId: "",
    customerName: "",
    projectId: "",
    projectName: "",
    executionDate: "",
    year: "",
    paymentMethod: "",
    deliveryDate: "",
    signingUnitId: "",
    officeId: "",
    groupLeaderId: "",
    isInvoice: 0,
    isBid: 0,
    bidPrice: "",
    unbidReason: "",
    contractOriginalReturnDate: "",
    remarks: "",
    entryPerson: "",
    entryPersonName: "",
    entryDate: "",
  });
  const showPicker = ref(false);
  const showDatePicker = ref(false);
  const pickerDateValue = ref(Date.now());
  const showCustomerPicker = ref(false);
  const userList = ref([]);
  const customerOption = ref([]);
  const projectOptions = ref([]);
  const deptOptions = ref([]);
  const userOptions = ref([]);
  const userActionList = computed(() => {
    return userList.value.map(user => ({
      name: user.text,
      value: user.value,
    }));
  });
  const formatter = (type, value) => {
    if (type === "year") {
      return `${value}`;
    }
    if (type === "month") {
      return `${value}`;
    }
    if (type === "day") {
      return `${value}`;
    }
    return value;
  };
  const customerActionList = computed(() => {
    return customerOption.value.map(customer => ({
      name: customer.text,
      value: customer.value,
    }));
  });
  // 日期选择列表已移除,改用 up-datetime-picker
  // 产品大类选择列表
  const categoryActionList = computed(() => {
@@ -444,7 +545,7 @@
    customerName: [
      { required: true, message: "请选择客户名称", trigger: "change" },
    ],
    projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
    projectId: [{ required: true, message: "请选择项目", trigger: "change" }],
    executionDate: [
      { required: true, message: "请选择签订日期", trigger: "change" },
    ],
@@ -473,18 +574,85 @@
    form.value.salesman = item.name;
  };
  // 日期确认事件
  const onDateConfirm = e => {
    form.value.executionDate = formatDateToYMD(e.value);
    // 保持pickerDateValue为时间戳格式,而不是格式化的字符串
    pickerDateValue.value = e.value;
    showDatePicker.value = false;
  // 签订日期 → 年份自动取年份(管理端同此逻辑,年份只读)
  const onExecutionDateChange = value => {
    form.value.year = value ? Number(String(value).substring(0, 4)) : "";
  };
  // 客户选择事件
  const onCustomerSelect = item => {
    form.value.customerName = item.name;
    form.value.customerId = item.value;
  };
  // 需求五.4:签约金额 = 含税单价 × 数量,逐行汇总
  const productTotals = computed(() => {
    const sumBy = key =>
      (productData.value || [])
        .reduce((sum, row) => sum + (Number(row?.[key]) || 0), 0)
        .toFixed(2);
    return {
      taxInclusive: sumBy("taxInclusiveTotalPrice"),
      taxExclusive: sumBy("taxExclusiveTotalPrice"),
    };
  });
  // 需求五.1:选项目后带出客户与产品明细(管理端 fetchAndPopulateProject 同逻辑)
  const applyProject = async id => {
    const matched = projectOptions.value.find(
      item => String(item.value) === String(id)
    );
    form.value.projectName = matched?.name || "";
    if (!id) {
      productData.value = [];
      return;
    }
    try {
      const res = await getProject(id);
      const detail = res?.data?.data ?? res?.data ?? res;
      const info = detail?.info || {};
      if (info.clientId) form.value.customerId = info.clientId;
      if (info.clientName) form.value.customerName = info.clientName;
      const products = detail?.salesLedgerProductList;
      productData.value = Array.isArray(products)
        ? products.map(item => ({
            ...item,
            id: undefined,
            salesLedgerId: undefined,
          }))
        : [];
    } catch (error) {
      uni.showToast({ title: "获取项目信息失败", icon: "none" });
    }
  };
  // v-model 已先写入 projectId,取消时要回滚到上一个项目
  const previousProjectId = ref("");
  const confirmProjectChange = id => {
    previousProjectId.value = id;
    applyProject(id);
  };
  const onProjectChange = id => {
    if (productData.value && productData.value.length > 0) {
      uni.showModal({
        title: "提示",
        content: "当前已录入产品明细,切换项目将被覆盖,是否继续?",
        success: res => {
          if (res.confirm) {
            confirmProjectChange(id);
          } else {
            form.value.projectId = previousProjectId.value;
          }
        },
      });
      return;
    }
    confirmProjectChange(id);
  };
  // 原有的确认方法已被新的action-sheet选择方法替代
@@ -820,33 +988,55 @@
    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}-${month}-${day}`;
  };
  // 填充表单数据(编辑模式)
  const fillFormData = () => {
    if (!editData.value) return;
    getSalesLedgerWithProducts({ id: editData.value.id, type: 1 }).then(res => {
      productData.value = res.productData;
    });
    console.log(editData.value);
    // 填充基本信息
    form.value.salesContractNo = editData.value.salesContractNo || "";
    form.value.customerName = editData.value.customerName || "";
    form.value.projectName = editData.value.projectName || "";
    form.value.executionDate = editData.value.executionDate || "";
    form.value.paymentMethod = editData.value.paymentMethod || "";
    form.value.salesman = editData.value.salesman || "";
    form.value.entryPerson = editData.value.entryPerson || "";
    form.value.entryPersonName = editData.value.entryPersonName || "";
    form.value.entryDate = editData.value.entryDate || "";
    form.value.id = editData.value.id || "";
    form.value.customerId = editData.value.customerId || "";
    const row = editData.value;
    // 设置日期选择器的值
    if (editData.value.executionDate) {
      pickerDateValue.value = editData.value.executionDate;
    }
    // 先用列表行同步铺一遍,保证请求返回前表单不是空的
    form.value.salesContractNo = row.salesContractNo || "";
    form.value.customerName = row.customerName || "";
    form.value.customerId = row.customerId || "";
    form.value.projectId = row.projectId ?? "";
    form.value.projectName = row.projectName || "";
    form.value.executionDate = row.executionDate || "";
    form.value.paymentMethod = row.paymentMethod || "";
    form.value.deliveryDate = row.deliveryDate || "";
    form.value.signingUnitId = row.signingUnitId ?? "";
    form.value.officeId = row.officeId ?? "";
    form.value.groupLeaderId = row.groupLeaderId ?? "";
    form.value.isInvoice = row.isInvoice ?? 0;
    form.value.isBid = row.isBid ?? 0;
    form.value.bidPrice = row.bidPrice ?? "";
    form.value.unbidReason = row.unbidReason || "";
    form.value.contractOriginalReturnDate =
      row.contractOriginalReturnDate || "";
    form.value.remarks = row.remarks || "";
    form.value.salesman = row.salesman || "";
    form.value.entryPerson = row.entryPerson || "";
    form.value.entryPersonName = row.entryPersonName || "";
    form.value.entryDate = row.entryDate || "";
    form.value.year = row.year || "";
    form.value.id = row.id || "";
    previousProjectId.value = form.value.projectId;
    // 详情接口字段比列表行全,再整体覆盖一次补齐新增字段
    getSalesLedgerWithProducts({ id: row.id, type: 1 })
      .then(res => {
        const data = res?.data ?? res ?? {};
        productData.value = data.productData || [];
        form.value = { ...form.value, ...data, id: row.id };
        previousProjectId.value = form.value.projectId ?? "";
        if (form.value.executionDate) {
          form.value.year = Number(
            String(form.value.executionDate).substring(0, 4)
          );
        }
      })
      .catch(() => {
        uni.showToast({ title: "获取台账详情失败", icon: "none" });
      });
  };
  const getUserList = () => {
    userListNoPage().then(res => {
@@ -896,6 +1086,46 @@
    });
  };
  // 项目下拉(需求五.1 的联动入口)
  const getProjectOptions = () => {
    loadProjectOptions()
      .then(options => {
        projectOptions.value = options;
      })
      .catch(() => {
        projectOptions.value = [];
      });
  };
  // 签单单位 / 办事处共用部门下拉,组负责人用员工下拉
  const getOrgOptions = async () => {
    const [depts, users] = await Promise.all([
      getDept().catch(() => ({})),
      userListNoPageByTenantId().catch(() => ({})),
    ]);
    deptOptions.value = (depts?.data || []).map(item => ({
      name: item.deptName,
      value: item.deptId,
    }));
    const rows = users?.data || [];
    userOptions.value = (Array.isArray(rows) ? rows : []).map(item => ({
      name: item.nickName || item.userName || `用户${item.userId}`,
      value: item.userId,
    }));
  };
  // 需求三.6:招投标中标后带项目信息跳进来(utils/prefill.js 写入)
  const applyPrefillProject = () => {
    const prefill = takePrefillProject();
    if (!prefill) return;
    form.value.projectId = prefill.projectId ?? "";
    form.value.projectName = prefill.projectName || "";
    if (prefill.customerId) form.value.customerId = prefill.customerId;
    if (prefill.customerName) form.value.customerName = prefill.customerName;
    previousProjectId.value = form.value.projectId;
    if (form.value.projectId) applyProject(form.value.projectId);
  };
  onMounted(() => {
    // 获取页面参数
    operationType.value = uni.getStorageSync("operationType") || "";
@@ -906,9 +1136,13 @@
    getCustomerList();
    // 获取产品大类tree数据
    getProductOptions();
    // 获取项目 / 部门 / 员工下拉
    getProjectOptions();
    getOrgOptions();
    // 赋值默认信息
    if (operationType.value === "add") {
      setUserInfo();
      applyPrefillProject();
    }
    // 获取编辑数据并填充表单
@@ -932,4 +1166,41 @@
<style lang="scss">
  @import "@/static/scss/form-common.scss";
  .switch-row {
    display: flex;
    justify-content: flex-end;
    width: 100%;
  }
  .product-summary {
    margin: 12px 0 0;
    padding: 12px 14px;
    background: #f8fbff;
    border-radius: 10px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .summary-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .summary-label {
    font-size: 14px;
    color: #606266;
  }
  .summary-value {
    font-size: 15px;
    font-weight: 600;
    color: #22324d;
  }
  .summary-value.highlight {
    color: #2979ff;
  }
</style>