You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1592 lines
62 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="contract-sign-field">
<!-- 提交用隐藏字段 -->
<input :name="fieldName" type="hidden" :value="normalizedValue" />
<!-- 当前展示文本有值时展示 -->
<span v-if="hasValue && displayText" class="contract-sign-label" :title="displayText">
{{ displayText }}
</span>
<span
v-else-if="hasValue"
class="contract-sign-label contract-sign-label--muted"
:title="normalizedValue"
>
ID: {{ normalizedValue }}
</span>
<el-button v-if="!hasValue" type="primary" size="small" @click="openDialog">合同签订</el-button>
<el-button v-if="hasValue" type="primary" size="small" @click="openDialog">重新签订</el-button>
<el-dialog
title="合同签订"
:visible.sync="dialogVisible"
width="75%"
:close-on-click-modal="false"
append-to-body
@open="onDialogOpen"
@close="onDialogClose"
>
<el-form
:model="form"
:rules="rules"
ref="formRef"
label-width="140px"
v-loading="loading"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同编号" prop="contract_no_without_prefix">
<el-input v-model="form.contract_no_without_prefix">
<template slot="prepend">CZHT</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同名称" prop="title">
<el-input v-model="form.title" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="合同主要内容" prop="main_content">
<el-input v-model="form.main_content" type="textarea" :rows="3" />
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="甲方" prop="party_a">
<el-input v-model="form.party_a" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="乙方" prop="party_b">
<el-input v-model="form.party_b" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同金额(元)" prop="amount_total" required>
<el-input-number
v-model="form.amount_total"
:min="0"
:precision="2"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目预算金额(元)" prop="budget_amount">
<el-input-number v-model="form.budget_amount" :min="0" :precision="2" style="width: 100%" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="金额类型" prop="amount_type">
<el-select v-model="form.amount_type" placeholder="请选择金额类型" style="width: 100%">
<el-option label="闭口合同(金额确定)" value="fixed" />
<el-option label="框架协议/开口合同(金额不确定)" value="open" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.amount_type === 'open'">
<el-form-item label="金额说明" prop="amount_description">
<el-input v-model="form.amount_description" type="textarea" :rows="2" placeholder="请说明开口合同的金额情况" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同类型" prop="contract_type_id">
<el-select v-model="form.contract_type_id" placeholder="请选择合同类型" filterable clearable style="width: 100%" @change="handleContractTypeChange">
<el-option
v-for="type in contractTypeOptions"
:key="type.id"
:label="type.name"
:value="type.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="签订日期" prop="sign_date">
<el-date-picker
v-model="form.sign_date"
type="date"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="申请日期" prop="apply_date">
<el-date-picker
v-model="form.apply_date"
type="date"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同履行期" prop="perform_period">
<el-input v-model="form.perform_period" placeholder="例如2025-01-01 至 2025-12-31" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="付款方式" prop="pay_method">
<el-input v-model="form.pay_method" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预算年度" prop="fund_source_year_id">
<el-select v-model="form.fund_source_year_id" placeholder="请选择预算年度" filterable clearable style="width: 100%" @change="handleFundSourceYearChange">
<el-option
v-for="year in budgetYearOptions"
:key="year.value"
:label="year.label"
:value="year.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="项目经费来源" prop="fund_source_budget_data_id">
<el-select v-model="form.fund_source_budget_data_id" placeholder="请选择项目经费来源" filterable clearable style="width: 100%">
<el-option-group
v-for="group in fundSourceOptions"
:key="group.label"
:label="group.label"
>
<el-option
v-for="option in group.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-option-group>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="是否为政府采购" prop="is_government_purchase">
<el-switch v-model="form.is_government_purchase" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="招标代理机构">
<el-input v-model="form.tender_agent" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购方式" prop="purchase_method_id">
<el-select v-model="form.purchase_method_id" placeholder="请选择采购方式" filterable clearable style="width: 100%">
<el-option
v-for="method in purchaseMethodOptions"
:key="method.id"
:label="method.name"
:value="method.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="合同履行情况">
<el-input v-model="form.perform_status" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否验收" prop="is_accepted">
<el-switch v-model="form.is_accepted" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="采购类别" prop="purchase_category_id">
<el-select v-model="form.purchase_category_id" placeholder="请选择采购类别" filterable clearable style="width: 100%">
<el-option
v-for="category in purchaseCategoryOptions"
:key="category.id"
:label="category.name"
:value="category.id"
/>
</el-select>
</el-form-item>
<el-form-item label="科室" prop="owner_department_ids_array">
<el-select v-model="form.owner_department_ids_array" multiple placeholder="请选择科室" filterable clearable style="width: 100%">
<el-option v-for="dept in departmentList" :key="dept.id" :label="dept.name" :value="dept.id.toString()" />
</el-select>
</el-form-item>
<el-form-item label="合同签订/变更经办人" prop="handler_admin_ids_array">
<el-select v-model="form.handler_admin_ids_array" multiple placeholder="请选择经办人" filterable clearable style="width: 100%">
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id.toString()" />
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="申请科室经办人" prop="apply_handler_id_array">
<el-select v-model="form.apply_handler_id_array" placeholder="请选择" filterable multiple clearable style="width: 100%">
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id.toString()" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="采购科室经办人" prop="purchase_handler_id">
<el-select v-model="form.purchase_handler_id" placeholder="请选择" filterable clearable style="width: 100%">
<el-option v-for="user in userList" :key="user.id" :label="user.name" :value="user.id" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" :rows="2" />
</el-form-item>
<el-form-item label="附件">
<el-upload
:action="uploadAction"
:headers="uploadHeaders"
:before-upload="beforeUpload"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleUploadRemove"
:file-list="attachmentFileList"
:limit="1"
>
<el-button type="primary">上传文件</el-button>
<template #tip>
<div class="el-upload__tip">只能上传 JPG/PNG/PDF/DOC/DOCX/XLS/XLSX 格式的文件,且不超过 10MB</div>
</template>
</el-upload>
</el-form-item>
<el-divider content-position="left">付款计划</el-divider>
<el-form-item prop="pay_plans" style="margin-bottom: 0;">
<div class="payplan-toolbar">
<el-button type="primary" size="small" @click="addPayPlan">新增计划</el-button>
</div>
<el-table :data="form.pay_plans" border size="small" class="mb-16 payplan-table" style="width: 100%">
<el-table-column prop="phase_no" label="期次" width="100">
<template #default="{ row }">
<el-input-number v-model="row.phase_no" :min="1" size="small" />
</template>
</el-table-column>
<el-table-column prop="due_date" label="计划付款日" width="150">
<template #default="{ row }">
<el-date-picker
v-model="row.due_date"
type="date"
value-format="yyyy-MM-dd"
size="small"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column prop="amount_plan" label="计划金额" width="140">
<template #default="{ row }">
<el-input-number
v-model="row.amount_plan"
:min="0"
:step="1000"
size="small"
style="width: 100%"
/>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="{ $index }">
<el-button type="danger" link size="small" @click="removePayPlan($index)">移除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="handleSubmit"></el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import request from "@/utils/request";
import { Message } from 'element-ui';
import { getToken } from '@/utils/auth';
import {
getFlowDetailForContract,
getContractSettings,
getContractByFlowId,
createContract,
getBudgetYearOptions,
} from "@/api/flow";
import { userListNoAuth, departmentListNoAuth } from "@/api/common";
export default {
name: "ContractSignField",
props: {
fieldName: { type: String, required: true },
value: { type: [String, Number], default: "" },
display: { type: String, default: "" },
flowId: { type: [String, Number], default: "" },
},
data() {
return {
dialogVisible: false,
loading: false,
saving: false,
formRef: null,
userList: [],
departmentList: [],
attachmentFileList: [],
budgetYearOptions: [],
fundSourceOptions: [],
purchaseCategoryOptions: [],
contractTypeOptions: [],
purchaseMethodOptions: [],
suppressFundSourceYearWatcher: false,
form: {
contract_no: "",
contract_no_without_prefix: "",
title: "",
main_content: "",
party_a: "",
party_b: "",
amount_total: null,
amount_type: "fixed",
amount_description: "",
budget_amount: 0,
contract_type: "",
sign_date: "",
apply_date: "",
perform_period: "",
pay_method: "",
fund_source: "",
fund_source_year_id: null,
fund_source_budget_data_id: null,
is_government_purchase: false,
tender_agent: "",
purchase_method: "",
perform_status: "",
is_accepted: false,
purchase_category: "",
purchase_category_id: null,
handler_admin_ids: "",
handler_admin_ids_array: [],
apply_handler_id: "",
apply_handler_id_array: [],
purchase_handler_id: null,
owner_department_id: null,
owner_department_ids: "",
owner_department_ids_array: [],
remark: "",
attachment_id: null,
pay_plans: [],
},
rules: {},
};
},
computed: {
normalizedValue() {
return this.value === null || this.value === undefined ? "" : String(this.value);
},
hasValue() {
return this.normalizedValue !== "";
},
displayText() {
return this.hasValue ? this.display || "" : "";
},
uploadAction() {
// 使用环境变量中的上传API如果没有则使用默认路径
const uploadAPI = process.env.VUE_APP_UPLOAD_API;
if (uploadAPI) {
return uploadAPI;
}
// 如果没有配置,使用 baseURL + /upload-file
const baseURL = process.env.VUE_APP_BASE_API || '';
// 确保路径正确baseURL 可能是 /api 或 /api/,需要统一处理
const apiPath = baseURL.endsWith('/') ? baseURL.slice(0, -1) : baseURL;
return `${apiPath}/upload-file`;
},
uploadHeaders() {
const token = getToken();
return token ? { Authorization: `Bearer ${token}` } : {};
},
},
watch: {
'form.fund_source_year_id'(newVal, oldVal) {
// 当预算年度变化时,自动加载项目经费来源选项
if (newVal) {
if (!this.suppressFundSourceYearWatcher) {
this.fetchFundSourceOptions(newVal);
}
} else {
this.fundSourceOptions = [];
if (!this.suppressFundSourceYearWatcher) {
this.form.fund_source_budget_data_id = null;
}
}
// 如果年度变化了,清空之前选择的经费来源
if (newVal !== oldVal && !this.suppressFundSourceYearWatcher) {
this.form.fund_source_budget_data_id = null;
}
},
// 监听多选下拉变化,触发校验
'form.owner_department_ids_array'() {
if (this.$refs.formRef) {
this.$refs.formRef.validateField('owner_department_ids_array', () => {});
}
},
'form.handler_admin_ids_array'() {
if (this.$refs.formRef) {
this.$refs.formRef.validateField('handler_admin_ids_array', () => {});
}
},
'form.apply_handler_id_array'() {
if (this.$refs.formRef) {
this.$refs.formRef.validateField('apply_handler_id_array', () => {});
}
},
'form.pay_plans'() {
if (this.$refs.formRef) {
this.$refs.formRef.validateField('pay_plans', () => {});
}
},
// 监听金额类型变化,重新校验金额说明
'form.amount_type'() {
if (this.$refs.formRef) {
this.$refs.formRef.validateField('amount_description', () => {});
}
},
// 监听合同类型变化,动态调整预算年度和项目经费来源的必填校验
'form.contract_type_id'() {
// 当合同类型改变时,重新验证相关字段
if (this.$refs.formRef) {
this.$nextTick(() => {
this.$refs.formRef.validateField('fund_source_year_id');
this.$refs.formRef.validateField('fund_source_budget_data_id');
});
}
},
},
async mounted() {
// 组件初始化时如果value为空但有flowId尝试从流程数据恢复合同
if (!this.hasValue && this.flowId) {
try {
const res = await getContractByFlowId(this.flowId);
const contractData = res?.data || res;
if (contractData && contractData.id) {
// 找到已创建的合同自动更新value和display
const displayText = contractData.contract_no + (contractData.title ? ` / ${contractData.title}` : "");
this.$emit("input", String(contractData.id));
this.$emit("update:display", displayText);
}
} catch (e) {
// 静默失败,不影响组件正常使用
console.warn("从流程数据恢复合同时出错:", e);
}
}
},
methods: {
openDialog() {
this.dialogVisible = true;
},
// 判断是否为编辑模式已有合同ID
isEditMode() {
return this.hasValue && this.normalizedValue && parseInt(this.normalizedValue);
},
// 初始化校验规则
initRules() {
// 新建时:只校验到付款方式(包含付款方式)
// 注意:新建时不校验合同编号(不显示红色星号);编辑时仍保持必填校验
const baseRulesCommon = {
title: [{ required: true, message: "请填写合同名称", trigger: "blur" }],
main_content: [{ required: true, message: "请填写合同主要内容", trigger: "blur" }],
party_a: [{ required: true, message: "请填写甲方", trigger: "blur" }],
party_b: [{ required: true, message: "请填写乙方", trigger: "blur" }],
amount_total: [{ required: true, message: "请填写合同金额", trigger: ["blur", "change"] }],
amount_type: [{ required: true, message: "请选择金额类型", trigger: "change" }],
amount_description: [
{
validator: (rule, value, callback) => {
if (this.form.amount_type === 'open' && !value) {
callback(new Error('开口合同时,金额说明不能为空'));
} else {
callback();
}
},
trigger: 'blur'
}
],
budget_amount: [
{ required: true, message: "请填写项目预算金额", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (value === null || value === undefined || value === '') {
callback(new Error('请填写项目预算金额'));
} else if (value === 0) {
callback(new Error('项目预算金额不能为0'));
} else {
callback();
}
},
trigger: 'blur'
}
],
contract_type_id: [{ required: true, message: "请选择合同类型", trigger: "change" }],
sign_date: [{ required: true, message: "请选择签订日期", trigger: "change" }],
apply_date: [{ required: true, message: "请选择申请日期", trigger: "change" }],
perform_period: [{ required: true, message: "请填写合同履行期", trigger: "blur" }],
pay_method: [{ required: true, message: "请填写付款方式", trigger: "blur" }],
};
// 新建时只校验到付款方式(包含付款方式)
if (!this.isEditMode()) {
this.rules = {
...baseRulesCommon,
};
return;
}
// 编辑时:合同编号必填
const baseRules = {
contract_no_without_prefix: [{ required: true, message: "请填写合同编号", trigger: "blur" }],
...baseRulesCommon,
};
// 编辑时全量校验(与合同编辑页面对齐)
this.rules = {
...baseRules,
fund_source_year_id: [
{
validator: (rule, value, callback) => {
// 默认必填,只有当合同类型明确是"收入类"时才撤销必填校验
const contractTypeName = this.getContractTypeName();
if (contractTypeName === '收入类') {
// 收入类不需要必填
callback();
} else {
// 其他情况(包括未选择合同类型)都需要必填
if (!value) {
callback(new Error('请选择预算年度'));
} else {
callback();
}
}
},
trigger: 'change'
}
],
fund_source_budget_data_id: [
{
validator: (rule, value, callback) => {
// 默认必填,只有当合同类型明确是"收入类"时才撤销必填校验
const contractTypeName = this.getContractTypeName();
if (contractTypeName === '收入类') {
// 收入类不需要必填
callback();
} else {
// 其他情况(包括未选择合同类型)都需要必填
if (!value) {
callback(new Error('请选择项目经费来源'));
} else {
callback();
}
}
},
trigger: 'change'
}
],
is_government_purchase: [{ required: true, message: "请选择是否为政府采购", trigger: "change" }],
purchase_category_id: [{ required: true, message: "请选择采购类别", trigger: "change" }],
purchase_method_id: [{ required: true, message: "请选择采购方式", trigger: "change" }],
is_accepted: [{ required: true, message: "请选择是否验收", trigger: "change" }],
handler_admin_ids_array: [
{
required: true,
validator: (rule, value, callback) => {
const ids = Array.isArray(value) ? value : [];
if (ids.length === 0) {
callback(new Error('请选择合同签订/变更经办人'));
} else {
callback();
}
},
trigger: ['change', 'blur']
}
],
apply_handler_id_array: [
{
required: true,
validator: (rule, value, callback) => {
const ids = Array.isArray(value) ? value : [];
if (ids.length === 0) {
callback(new Error('请选择申请科室经办人'));
} else {
callback();
}
},
trigger: ['change', 'blur']
}
],
owner_department_ids_array: [
{
required: true,
validator: (rule, value, callback) => {
const ids = Array.isArray(value) ? value : [];
if (ids.length === 0) {
callback(new Error('请选择科室'));
} else {
callback();
}
},
trigger: ['change', 'blur']
}
],
purchase_handler_id: [{ required: true, message: "请选择采购科室经办人", trigger: "change" }],
pay_plans: [
{
validator: (rule, value, callback) => {
if (!value || !Array.isArray(value) || value.length === 0) {
callback(new Error('至少需要一条付款计划'));
return;
}
const errors = [];
value.forEach((plan, index) => {
const planErrors = [];
if (!plan.phase_no || plan.phase_no <= 0) {
planErrors.push('期次');
}
if (!plan.due_date) {
planErrors.push('到期日期');
}
if (!plan.amount_plan || plan.amount_plan <= 0) {
planErrors.push('金额');
}
if (planErrors.length > 0) {
errors.push(`${index + 1}条付款计划的${planErrors.join('、')}不能为空`);
}
});
if (errors.length > 0) {
callback(new Error(errors[0]));
} else {
callback();
}
},
trigger: ['change', 'blur']
}
]
};
},
// 获取合同类型名称
getContractTypeName() {
if (!this.form.contract_type_id) {
return null;
}
const contractType = this.contractTypeOptions.find(type => type.id === this.form.contract_type_id);
return contractType ? contractType.name : null;
},
handleFundSourceYearChange() {
this.form.fund_source_budget_data_id = null;
if (this.form.fund_source_year_id) {
this.fetchFundSourceOptions(this.form.fund_source_year_id);
}
},
// 处理合同类型变化
handleContractTypeChange() {
// 当合同类型改变时,重新验证预算年度和项目经费来源
if (this.$refs.formRef) {
this.$nextTick(() => {
this.$refs.formRef.validateField('fund_source_year_id');
this.$refs.formRef.validateField('fund_source_budget_data_id');
});
}
},
async fetchBudgetYearOptions() {
try {
const response = await getBudgetYearOptions(false);
const arr = Array.isArray(response) ? response : (response?.data || []);
this.budgetYearOptions = arr.map((y) => ({
value: y.value,
label: y.label || (y.year ? `${y.year}` : `${y.value}`),
status: y.status, // 保留状态信息
}));
// 默认选择 ACTIVE 状态或最新的年度(与合同编辑页面保持一致)
// 注意:只在新建时默认选择,编辑时应该保持原有选择
if (this.budgetYearOptions.length > 0 && !this.form.fund_source_year_id && !this.isEditMode()) {
const activeYear = this.budgetYearOptions.find(y => y.status === 'ACTIVE');
if (activeYear) {
this.form.fund_source_year_id = activeYear.value;
await this.fetchFundSourceOptions(activeYear.value);
} else {
this.form.fund_source_year_id = this.budgetYearOptions[0].value;
await this.fetchFundSourceOptions(this.budgetYearOptions[0].value);
}
}
} catch (e) {
console.error('获取预算年度列表失败', e);
Message.error('获取预算年度列表失败:' + (e.message || '未知错误'));
this.budgetYearOptions = [];
}
},
async fetchFundSourceOptions(yearId) {
if (!yearId) {
this.fundSourceOptions = [];
return;
}
try {
// 注意:响应拦截器已经返回了 res.data所以 response 直接就是数据数组
const response = await request.get('/api/budget/contracts/fund-source-options', { params: { year_id: yearId } });
// 检查是否是数组格式(正常的响应)
if (Array.isArray(response)) {
this.fundSourceOptions = response;
} else if (response && response.code === 0 && Array.isArray(response.data)) {
// 兼容处理如果响应拦截器没有处理fallback 到原始格式
this.fundSourceOptions = response.data;
} else {
this.fundSourceOptions = [];
}
} catch (e) {
console.error('获取项目经费来源列表失败', e);
Message.error('获取项目经费来源列表失败:' + (e.message || '未知错误'));
this.fundSourceOptions = [];
}
},
async fetchBudgetDataDetail(budgetDataId) {
if (!budgetDataId) {
return null;
}
try {
const response = await request.get(`/api/budget/budget-data/${budgetDataId}`);
return response?.data || response || null;
} catch (e) {
console.error('获取项目经费来源详情失败', e);
return null;
}
},
async restoreFundSourceByBudgetDataId(budgetDataId) {
if (!budgetDataId) {
return;
}
const budgetData = await this.fetchBudgetDataDetail(budgetDataId);
const yearId = budgetData?.year_id || budgetData?.budget_year?.id || budgetData?.budgetYear?.id;
if (yearId) {
this.suppressFundSourceYearWatcher = true;
this.form.fund_source_year_id = yearId;
await this.fetchFundSourceOptions(yearId);
this.form.fund_source_budget_data_id = budgetDataId;
this.$nextTick(() => {
this.suppressFundSourceYearWatcher = false;
});
}
},
async fetchPurchaseCategoryOptions() {
try {
// 注意:响应拦截器已经返回了 res.data所以 response 直接就是数据
const response = await request.get('/api/budget/purchase-categories');
// 与合同编辑页面保持一致:只获取启用的采购类别
if (Array.isArray(response)) {
this.purchaseCategoryOptions = response;
} else if (response && response.code === 0 && Array.isArray(response.data)) {
// 兼容处理如果响应拦截器没有处理fallback 到原始格式
this.purchaseCategoryOptions = response.data;
} else {
this.purchaseCategoryOptions = [];
}
} catch (e) {
console.error('获取采购类别列表失败', e);
Message.error('获取采购类别列表失败:' + (e.message || '未知错误'));
this.purchaseCategoryOptions = [];
}
},
async fetchContractTypeOptions() {
try {
// 注意:响应拦截器已经返回了 res.data所以 response 直接就是数据
// 与合同编辑页面保持一致:只获取启用的合同类型(使用 /api/budget/contract-types 而不是 /all
const response = await request.get('/api/budget/contract-types');
if (Array.isArray(response)) {
this.contractTypeOptions = response;
} else if (response && response.code === 0 && Array.isArray(response.data)) {
// 兼容处理如果响应拦截器没有处理fallback 到原始格式
this.contractTypeOptions = response.data;
} else {
this.contractTypeOptions = [];
}
} catch (e) {
console.error('获取合同类型列表失败', e);
Message.error('获取合同类型列表失败:' + (e.message || '未知错误'));
this.contractTypeOptions = [];
}
},
async fetchPurchaseMethodOptions() {
try {
// 注意:响应拦截器已经返回了 res.data所以 response 直接就是数据
// 与合同编辑页面保持一致:只获取启用的采购方式(使用 /api/budget/purchase-methods 而不是 /all
const response = await request.get('/api/budget/purchase-methods');
if (Array.isArray(response)) {
this.purchaseMethodOptions = response;
} else if (response && response.code === 0 && Array.isArray(response.data)) {
// 兼容处理如果响应拦截器没有处理fallback 到原始格式
this.purchaseMethodOptions = response.data;
} else {
this.purchaseMethodOptions = [];
}
} catch (e) {
console.error('获取采购方式列表失败', e);
Message.error('获取采购方式列表失败:' + (e.message || '未知错误'));
this.purchaseMethodOptions = [];
}
},
beforeUpload(file) {
const isValidType = ['image/jpeg', 'image/png', 'image/jpg', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'].includes(file.type);
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isValidType) {
Message.error('只能上传 JPG/PNG/PDF/DOC/DOCX/XLS/XLSX 格式的文件!');
return false;
}
if (!isLt10M) {
Message.error('文件大小不能超过 10MB!');
return false;
}
return true;
},
handleUploadSuccess(response) {
if (response.code === 0) {
const fileData = response.data;
this.form.attachment_id = fileData.id;
this.attachmentFileList = [{
name: fileData.original_name || fileData.name || '文件',
url: fileData.url || (fileData.folder && fileData.name ? `${fileData.folder}/${fileData.name}` : ''),
uid: fileData.id,
id: fileData.id
}];
Message.success('上传成功');
} else {
Message.error('上传失败:' + (response.msg || '未知错误'));
}
},
handleUploadError(error) {
console.error('上传错误:', error);
Message.error('上传失败,请重试');
},
handleUploadRemove() {
this.form.attachment_id = null;
this.attachmentFileList = [];
Message.success('已移除文件');
},
async loadUserList() {
try {
// 使用 common.js 中的函数,确保路径正确
// 注意:响应拦截器返回的是 res.data所以这里 response 已经是 data 部分
const response = await userListNoAuth({ rows: 1000 });
// simpleUser 返回分页数据,格式: { data: [...], current_page: 1, ... }
if (response && response.data && Array.isArray(response.data)) {
this.userList = response.data;
} else if (Array.isArray(response)) {
// 兼容直接返回数组的情况
this.userList = response;
} else {
this.userList = [];
}
} catch (e) {
console.error('获取用户列表失败', e);
Message.error('获取用户列表失败:' + (e.message || '未知错误'));
this.userList = [];
}
},
async loadDepartmentList() {
try {
// 使用 common.js 中的函数,确保路径正确
// 注意:响应拦截器返回的是 res.data所以这里 response 已经是 data 部分(数组)
const response = await departmentListNoAuth({ rows: 1000 });
// simpleDepartment 直接返回数组
if (Array.isArray(response)) {
this.departmentList = response;
} else {
this.departmentList = [];
}
} catch (e) {
console.error('获取部门列表失败', e);
Message.error('获取部门列表失败:' + (e.message || '未知错误'));
this.departmentList = [];
}
},
async onDialogOpen() {
this.resetForm();
await this.loadUserList();
await this.loadDepartmentList();
await this.fetchBudgetYearOptions();
await this.fetchPurchaseCategoryOptions();
await this.fetchContractTypeOptions();
await this.fetchPurchaseMethodOptions();
// 初始化校验规则
this.initRules();
// 清除表单校验状态,避免新建时触发校验
this.$nextTick(() => {
if (this.$refs.formRef) {
this.$refs.formRef.clearValidate();
}
});
// 优先尝试加载已有合同通过合同ID或flowId
const contractExists = await this.loadExistingContract();
// 如果找不到已有合同且提供了flowId则从流程数据预填
if (!contractExists && this.flowId) {
await this.loadFlowDataAndPrefill();
} else if (contractExists && this.flowId && this.isEditMode()) {
await this.loadFlowDataAndPrefill({
onlyEmpty: true,
fields: this.getResignSupplementFields(),
});
}
// 重新初始化校验规则(因为可能已经加载了合同数据,需要区分新建/编辑)
this.initRules();
// 再次清除校验状态,确保填充数据后不会触发校验
this.$nextTick(() => {
if (this.$refs.formRef) {
// 如果是编辑模式,在数据加载完成后,需要正确设置多选字段的校验状态
if (this.isEditMode()) {
// 延迟一下确保DOM已更新和数据已填充
this.$nextTick(() => {
if (this.$refs.formRef) {
// 对于多选字段,如果数组有值,清除校验错误;如果为空,触发校验显示错误
// 这样确保编辑模式下,已填充的字段不显示错误,未填充的字段显示错误
const fieldsToCheck = [
{ prop: 'owner_department_ids_array', arrayField: 'owner_department_ids_array' },
{ prop: 'handler_admin_ids_array', arrayField: 'handler_admin_ids_array' },
{ prop: 'apply_handler_id_array', arrayField: 'apply_handler_id_array' },
];
fieldsToCheck.forEach(({ prop, arrayField }) => {
const ids = this.form[arrayField];
if (ids && Array.isArray(ids) && ids.length > 0) {
// 有值,清除该字段的校验错误
this.$refs.formRef.clearValidate(prop);
} else {
// 无值,先清除之前的错误状态,然后触发校验显示错误
// 这样可以确保校验状态正确更新
this.$refs.formRef.clearValidate(prop);
this.$nextTick(() => {
if (this.$refs.formRef) {
this.$refs.formRef.validateField(prop, () => {});
}
});
}
});
}
});
} else {
// 新建模式,清除所有校验状态
this.$refs.formRef.clearValidate();
}
}
});
},
onDialogClose() {
// 关闭时不做处理
},
resetForm() {
this.form = {
contract_no: "",
contract_no_without_prefix: "",
title: "",
main_content: "",
party_a: "",
party_b: "",
amount_total: null,
amount_type: "fixed",
amount_description: "",
budget_amount: 0,
contract_type_id: null,
contract_type: "", // 保留旧字段,用于兼容
sign_date: "",
apply_date: "",
perform_period: "",
pay_method: "",
fund_source: "",
fund_source_year_id: null,
fund_source_budget_data_id: null,
is_government_purchase: false,
tender_agent: "",
purchase_method_id: null,
purchase_method: "", // 保留旧字段,用于兼容
perform_status: "",
is_accepted: false,
purchase_category: "",
purchase_category_id: null,
handler_admin_ids: "",
handler_admin_ids_array: [],
apply_handler_id: "",
apply_handler_id_array: [],
purchase_handler_id: null,
owner_department_id: null,
owner_department_ids: "",
owner_department_ids_array: [],
remark: "",
attachment_id: null,
pay_plans: [],
};
this.attachmentFileList = [];
this.fundSourceOptions = [];
if (this.$refs.formRef) {
this.$refs.formRef.clearValidate();
}
},
async loadExistingContract() {
this.loading = true;
try {
let contractData = null;
// 优先通过合同ID加载如果字段已有值
if (this.hasValue && this.normalizedValue) {
try {
const contractId = parseInt(this.normalizedValue);
if (contractId) {
// 通过合同ID获取合同详情使用完整路径与其他API调用保持一致
const res = await request.get(`/api/budget/contracts/${contractId}`);
// 响应拦截器返回的是 res.data所以这里 res 已经是合同数据
if (res && res.id) {
contractData = res;
}
}
} catch (e) {
console.warn('通过合同ID加载失败尝试通过flowId加载:', e);
}
}
// 如果通过ID加载失败且提供了flowId尝试通过flowId加载
if (!contractData && this.flowId) {
const res = await getContractByFlowId(this.flowId);
contractData = res?.data || res;
}
if (contractData) {
// 处理合同编号拆分CZHT前缀
if (contractData.contract_no) {
if (contractData.contract_no.startsWith('CZHT')) {
this.form.contract_no_without_prefix = contractData.contract_no.substring(4);
} else {
this.form.contract_no_without_prefix = contractData.contract_no;
}
}
this.form.contract_no = contractData.contract_no || "";
this.form.title = contractData.title || "";
this.form.main_content = contractData.main_content || "";
this.form.party_a = contractData.party_a || "";
this.form.party_b = contractData.party_b || "";
this.form.amount_total = contractData.amount_total !== null && contractData.amount_total !== undefined ? contractData.amount_total : null;
this.form.amount_type = contractData.amount_type || "fixed";
this.form.amount_description = contractData.amount_description || "";
this.form.budget_amount = contractData.budget_amount || 0;
// 优先使用ID字段如果不存在则使用旧字段兼容历史数据
this.form.contract_type_id = contractData.contract_type_id || null;
this.form.contract_type = contractData.contract_type || ""; // 保留旧字段,用于兼容
this.form.sign_date = contractData.sign_date || "";
this.form.apply_date = contractData.apply_date || "";
this.form.perform_period = contractData.perform_period || "";
this.form.pay_method = contractData.pay_method || "";
this.form.fund_source = contractData.fund_source || "";
this.form.fund_source_year_id = contractData.fund_source_year_id || null;
this.form.fund_source_budget_data_id = contractData.fund_source_budget_data_id || null;
this.form.is_government_purchase = contractData.is_government_purchase ?? null;
this.form.tender_agent = contractData.tender_agent || "";
// 优先使用ID字段如果不存在则使用旧字段兼容历史数据
this.form.purchase_method_id = contractData.purchase_method_id || null;
this.form.purchase_method = contractData.purchase_method || ""; // 保留旧字段,用于兼容
this.form.perform_status = contractData.perform_status || "";
this.form.is_accepted = contractData.is_accepted ?? null;
this.form.purchase_category = contractData.purchase_category || "";
this.form.purchase_category_id = contractData.purchase_category_id || null;
this.form.apply_handler_id = contractData.apply_handler_id || "";
this.form.purchase_handler_id = contractData.purchase_handler_id || null;
this.form.owner_department_id = contractData.owner_department_id || null;
this.form.remark = contractData.remark || "";
this.form.attachment_id = contractData.attachment_id || null;
// 项目经费来源优先:可从 budget_data_id 反推预算年度,预算年度只用于加载下拉选项。
if (this.form.fund_source_budget_data_id) {
await this.restoreFundSourceByBudgetDataId(this.form.fund_source_budget_data_id);
} else if (this.form.fund_source_year_id) {
await this.fetchFundSourceOptions(this.form.fund_source_year_id);
}
// 处理科室多选转换为字符串数组因为下拉组件的value是字符串
if (contractData.owner_department_ids) {
const ids = typeof contractData.owner_department_ids === 'string'
? contractData.owner_department_ids.split(',').filter(id => id)
: (Array.isArray(contractData.owner_department_ids) ? contractData.owner_department_ids : []);
this.form.owner_department_ids_array = ids.map(id => String(id));
} else {
this.form.owner_department_ids_array = [];
}
// 处理经办人多选转换为字符串数组因为下拉组件的value是字符串
if (contractData.handler_admin_ids) {
const ids = typeof contractData.handler_admin_ids === 'string'
? contractData.handler_admin_ids.split(',').filter(id => id)
: (Array.isArray(contractData.handler_admin_ids) ? contractData.handler_admin_ids : []);
this.form.handler_admin_ids_array = ids.map(id => String(id));
} else {
this.form.handler_admin_ids_array = [];
}
// 处理申请科室经办人多选转换为字符串数组因为下拉组件的value是字符串
if (contractData.apply_handler_id) {
const ids = typeof contractData.apply_handler_id === 'string'
? contractData.apply_handler_id.split(',').filter(id => id)
: (Array.isArray(contractData.apply_handler_id) ? contractData.apply_handler_id : []);
this.form.apply_handler_id_array = ids.map(id => String(id));
} else {
this.form.apply_handler_id_array = [];
}
// 处理附件
if (contractData.attachment_id && contractData.attachment) {
this.attachmentFileList = [{
name: contractData.attachment.original_name || contractData.attachment.name || '文件',
url: contractData.attachment.url || (contractData.attachment.folder && contractData.attachment.name ? `${contractData.attachment.folder}/${contractData.attachment.name}` : ''),
uid: contractData.attachment.id,
id: contractData.attachment.id
}];
} else {
this.attachmentFileList = [];
}
// 填充付款计划
if (contractData.pay_plans && Array.isArray(contractData.pay_plans)) {
this.form.pay_plans = contractData.pay_plans.map((plan) => ({
phase_no: plan.phase_no || 1,
due_date: plan.due_date || "",
amount_plan: plan.amount_plan || 0,
}));
} else {
this.form.pay_plans = [];
}
return true;
}
return false;
} catch (e) {
console.error("加载已有合同失败:", e);
Message.error("加载已有合同失败");
return false;
} finally {
this.loading = false;
}
},
getResignSupplementFields() {
return [
'fund_source',
'fund_source_year_id',
'fund_source_budget_data_id',
'is_government_purchase',
'tender_agent',
'purchase_method_id',
'purchase_method',
'perform_status',
'is_accepted',
'purchase_category',
'purchase_category_id',
'handler_admin_ids',
'apply_handler_id',
'purchase_handler_id',
'owner_department_id',
'owner_department_ids',
'remark',
'attachment_id',
'pay_plans',
];
},
isEmptyPrefillValue(value) {
if (value === null || value === undefined || value === '') {
return true;
}
if (Array.isArray(value)) {
return value.length === 0;
}
return false;
},
applyMappingTransform(value, map) {
if (!map || map.value_transform !== 'json_field') {
return value;
}
const jsonField = map.json_field || 'id';
let parsed = value;
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed);
} catch (e) {
return null;
}
}
if (Array.isArray(parsed)) {
parsed = parsed[0] || null;
}
if (!parsed || typeof parsed !== 'object') {
return null;
}
return parsed[jsonField] ?? null;
},
getFirstJsonObject(value) {
let parsed = value;
if (typeof parsed === 'string') {
try {
parsed = JSON.parse(parsed);
} catch (e) {
return null;
}
}
if (Array.isArray(parsed)) {
parsed = parsed[0] || null;
}
return parsed && typeof parsed === 'object' ? parsed : null;
},
resolveMappedValue(rawValue, map, budgetField) {
const transformed = this.applyMappingTransform(rawValue, map);
if (budgetField === 'attachment_id' && (transformed === rawValue || transformed === null || transformed === undefined)) {
const fileInfo = this.getFirstJsonObject(rawValue);
return fileInfo?.id ?? transformed;
}
return transformed;
},
async setPrefillValue(field, value, onlyEmpty = false, rawValue = null) {
if (!Object.prototype.hasOwnProperty.call(this.form, field) || value === undefined || value === null || value === '') {
return;
}
if (onlyEmpty && !this.isEmptyPrefillValue(this.form[field])) {
return;
}
if (field === 'fund_source_budget_data_id') {
await this.restoreFundSourceByBudgetDataId(value);
return;
}
this.form[field] = value;
if (field === 'owner_department_ids') {
const ids = typeof value === 'string'
? value.split(',').filter(id => id)
: (Array.isArray(value) ? value : []);
this.form.owner_department_ids_array = ids.map(id => String(id));
if (!this.form.owner_department_id && ids.length > 0) {
this.form.owner_department_id = parseInt(ids[0]);
}
}
if (field === 'handler_admin_ids') {
const ids = typeof value === 'string'
? value.split(',').filter(id => id)
: (Array.isArray(value) ? value : []);
this.form.handler_admin_ids_array = ids.map(id => String(id));
}
if (field === 'apply_handler_id') {
const ids = typeof value === 'string'
? value.split(',').filter(id => id)
: (Array.isArray(value) ? value : []);
this.form.apply_handler_id_array = ids.map(id => String(id));
}
if (field === 'attachment_id') {
const fileInfo = this.getFirstJsonObject(rawValue);
this.attachmentFileList = fileInfo ? [{
name: fileInfo.original_name || fileInfo.name || '文件',
url: fileInfo.url || (fileInfo.folder && fileInfo.name ? `${fileInfo.folder}/${fileInfo.name}` : ''),
uid: fileInfo.id || value,
id: fileInfo.id || value
}] : [];
}
if (field === 'fund_source_year_id' && value) {
await this.fetchFundSourceOptions(value);
}
},
async loadFlowDataAndPrefill(options = {}) {
if (!this.flowId) {
return;
}
this.loading = true;
try {
const [flowDetailRes, settingsRes] = await Promise.all([
getFlowDetailForContract(this.flowId),
getContractSettings(this.flowId),
]);
const flowDetail = flowDetailRes?.data || flowDetailRes;
const settings = settingsRes?.data || settingsRes;
if (!flowDetail?.flow?.data) {
return;
}
const flowData = flowDetail.flow.data;
const contractMapping = (settings?.contract_field_mapping || []);
const payplanMapping = (settings?.payplan_field_mapping || []);
const onlyEmpty = !!options.onlyEmpty;
const allowedFields = Array.isArray(options.fields) ? options.fields : null;
if (contractMapping && contractMapping.length > 0) {
for (const map of contractMapping) {
const budgetField = map.budget_field;
const oaField = map.oa_field;
if (allowedFields && !allowedFields.includes(budgetField)) {
continue;
}
if (budgetField && oaField && flowData[oaField] !== undefined) {
await this.setPrefillValue(budgetField, this.resolveMappedValue(flowData[oaField], map, budgetField), onlyEmpty, flowData[oaField]);
}
}
}
const shouldFillPayPlans = !allowedFields || allowedFields.includes('pay_plans');
if (shouldFillPayPlans && (!onlyEmpty || this.isEmptyPrefillValue(this.form.pay_plans)) && settings.oa_custom_model_id_for_payplan && flowDetail.customModel) {
const payplanField = flowDetail.customModel.fields.find(
(f) => f.type === "relation" && f.sub_custom_model_id === settings.oa_custom_model_id_for_payplan
);
if (payplanField && flowData[payplanField.name]) {
let payplanRows = flowData[payplanField.name];
if (typeof payplanRows === "string") {
try {
payplanRows = JSON.parse(payplanRows);
} catch (e) {
payplanRows = [];
}
}
if (Array.isArray(payplanRows) && payplanRows.length > 0) {
this.form.pay_plans = this.mapPayPlans(payplanRows, payplanMapping);
}
}
} else if (shouldFillPayPlans && (!onlyEmpty || this.isEmptyPrefillValue(this.form.pay_plans)) && flowData.pay_plans && Array.isArray(flowData.pay_plans)) {
this.form.pay_plans = this.mapPayPlans(flowData.pay_plans, payplanMapping);
}
} catch (e) {
console.error("加载流程数据失败:", e);
Message.error("加载流程数据失败");
} finally {
this.loading = false;
}
},
mapPayPlans(rows, mapping) {
if (!Array.isArray(rows) || !Array.isArray(mapping) || mapping.length === 0) {
return [];
}
return rows.map((row, index) => {
const item = {};
mapping.forEach((map) => {
const budgetField = map.budget_field;
const oaField = map.oa_field;
if (budgetField && oaField && row[oaField] !== undefined) {
item[budgetField] = this.applyMappingTransform(row[oaField], map);
}
});
if (!item.phase_no) {
item.phase_no = index + 1;
}
if (item.amount_plan === undefined) {
item.amount_plan = 0;
}
return item;
});
},
addPayPlan() {
const maxPhaseNo =
this.form.pay_plans.length > 0
? Math.max(...this.form.pay_plans.map((p) => p.phase_no || 0))
: 0;
this.form.pay_plans.push({
phase_no: maxPhaseNo + 1,
due_date: "",
amount_plan: 0,
});
},
removePayPlan(index) {
this.form.pay_plans.splice(index, 1);
},
async handleSubmit() {
if (!this.$refs.formRef) {
return;
}
this.$refs.formRef.validate(async (valid) => {
if (!valid) {
return false;
}
this.saving = true;
try {
// 处理合同编号拼接CZHT前缀
let contractNo = this.form.contract_no_without_prefix || '';
if (contractNo && !contractNo.startsWith('CZHT')) {
contractNo = 'CZHT' + contractNo;
}
// 处理科室多选:将数组转换为逗号分隔的字符串
const ownerDepartmentIds = Array.isArray(this.form.owner_department_ids_array)
? this.form.owner_department_ids_array.join(',')
: (this.form.owner_department_ids || '');
// 如果有科室多选,取第一个作为 owner_department_id用于兼容
let ownerDepartmentId = null;
if (Array.isArray(this.form.owner_department_ids_array) && this.form.owner_department_ids_array.length > 0) {
ownerDepartmentId = parseInt(this.form.owner_department_ids_array[0]);
} else if (this.form.owner_department_id) {
ownerDepartmentId = this.form.owner_department_id;
}
// 处理经办人多选:将数组转换为逗号分隔的字符串
const handlerAdminIds = Array.isArray(this.form.handler_admin_ids_array)
? this.form.handler_admin_ids_array.join(',')
: (this.form.handler_admin_ids || '');
// 申请科室经办人多选:将数组转换为逗号分隔的字符串
const applyHandlerIds = Array.isArray(this.form.apply_handler_id_array)
? this.form.apply_handler_id_array.filter(id => id).join(',')
: (this.form.apply_handler_id || '');
// 如果已有合同ID传递合同ID用于更新
const contractId = this.hasValue && this.normalizedValue ? parseInt(this.normalizedValue) : null;
const payload = {
contract_id: contractId, // 如果提供,则更新该合同
flow_id: this.flowId ? parseInt(this.flowId) : null,
contract: {
contract_no: contractNo,
title: this.form.title,
main_content: this.form.main_content || "",
party_a: this.form.party_a || "",
party_b: this.form.party_b || "",
amount_total: this.form.amount_total || 0,
amount_type: this.form.amount_type,
amount_description: this.form.amount_description || "",
budget_amount: this.form.budget_amount || 0,
contract_type_id: this.form.contract_type_id,
contract_type: this.form.contract_type || "", // 保留旧字段,用于兼容
sign_date: this.form.sign_date || null,
apply_date: this.form.apply_date || null,
perform_period: this.form.perform_period || "",
pay_method: this.form.pay_method || "",
fund_source: this.form.fund_source || "",
fund_source_year_id: this.form.fund_source_year_id,
fund_source_budget_data_id: this.form.fund_source_budget_data_id,
is_government_purchase: this.form.is_government_purchase || false,
purchase_category_id: this.form.purchase_category_id,
tender_agent: this.form.tender_agent || "",
purchase_method_id: this.form.purchase_method_id,
purchase_method: this.form.purchase_method || "", // 保留旧字段,用于兼容
perform_status: this.form.perform_status || "",
is_accepted: this.form.is_accepted || false,
purchase_category: this.form.purchase_category || "",
handler_admin_ids: handlerAdminIds,
apply_handler_id: applyHandlerIds,
purchase_handler_id: this.form.purchase_handler_id,
owner_department_id: ownerDepartmentId,
owner_department_ids: ownerDepartmentIds,
remark: this.form.remark || "",
attachment_id: this.form.attachment_id,
},
pay_plans: this.form.pay_plans.map((p) => ({
phase_no: p.phase_no,
due_date: p.due_date || null,
amount_plan: p.amount_plan || 0,
})),
};
const res = await createContract(payload);
const data = res?.data || res;
if (data?.id) {
const displayText = data.contract_no + (data.title ? ` / ${data.title}` : "");
this.$emit("input", String(data.id));
this.$emit("update:display", displayText);
this.dialogVisible = false;
Message.success(contractId ? "合同更新成功" : "合同创建成功");
// 如果是新建合同不是更新且有flowId自动保存到流程字段轻量接口
if (!contractId && this.flowId && this.fieldName) {
try {
await request.post(`/api/oa/flow/save-field/${this.flowId}`, {
field_name: this.fieldName,
field_value: String(data.id),
});
// 静默保存,不显示提示
} catch (e) {
// 保存失败不影响主流程,只记录错误
console.error("保存合同ID到流程字段失败:", e);
}
}
} else {
Message.error(contractId ? "更新合同失败" : "创建合同失败");
}
} catch (e) {
console.error("创建合同失败:", e);
const msg = e?.response?.data?.msg || e?.message || "创建合同失败";
Message.error(msg);
} finally {
this.saving = false;
}
});
},
},
};
</script>
<style scoped>
.contract-sign-field {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.contract-sign-label {
color: #606266;
font-size: 14px;
flex: 1;
min-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.contract-sign-label--muted {
color: #909399;
}
.payplan-toolbar {
margin-bottom: 12px;
}
.mb-16 {
margin-bottom: 16px;
}
.payplan-table {
width: 100% !important;
}
.readonly-amount :deep(.el-input__inner) {
cursor: not-allowed;
background-color: #f5f7fa;
color: #909399;
}
.readonly-amount :deep(.el-input-number__decrease),
.readonly-amount :deep(.el-input-number__increase) {
display: none;
pointer-events: none;
}
</style>