更新
This commit is contained in:
73
src/api/purchase/purchasingRuleConfig.ts
Normal file
73
src/api/purchase/purchasingRuleConfig.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import request from "/@/utils/request"
|
||||
|
||||
const BASE_URL = '/purchase/purchasingruleconfig'
|
||||
|
||||
export function fetchList(query?: Object) {
|
||||
return request({
|
||||
url: `${BASE_URL}/page`,
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
export function getObj(id: string) {
|
||||
return request({
|
||||
url: `${BASE_URL}/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addObj(obj: Object) {
|
||||
return request({
|
||||
url: BASE_URL,
|
||||
method: 'post',
|
||||
data: obj
|
||||
})
|
||||
}
|
||||
|
||||
export function putObj(obj: Object) {
|
||||
return request({
|
||||
url: `${BASE_URL}/edit`,
|
||||
method: 'post',
|
||||
data: obj
|
||||
})
|
||||
}
|
||||
|
||||
export function delObjs(ids: string[]) {
|
||||
return request({
|
||||
url: `${BASE_URL}/delete`,
|
||||
method: 'post',
|
||||
data: ids
|
||||
})
|
||||
}
|
||||
|
||||
export function getRuleTypes() {
|
||||
return request({
|
||||
url: `${BASE_URL}/rule-types`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getEnabledRules() {
|
||||
return request({
|
||||
url: `${BASE_URL}/enabled`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export interface RuleEvaluateParams {
|
||||
budget: number
|
||||
isCentralized?: string
|
||||
isSpecial?: string
|
||||
hasSupplier?: string
|
||||
projectType?: string
|
||||
ruleType?: string
|
||||
}
|
||||
|
||||
export function evaluateRules(params: RuleEvaluateParams) {
|
||||
return request({
|
||||
url: `${BASE_URL}/evaluate`,
|
||||
method: 'post',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
110
src/hooks/usePurchaseRules.ts
Normal file
110
src/hooks/usePurchaseRules.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { evaluateRules, getEnabledRules } from '/@/api/purchase/purchasingRuleConfig';
|
||||
|
||||
const RULE_CACHE_KEY = 'purchase_rule_cache';
|
||||
const CACHE_EXPIRE_TIME = 5 * 60 * 1000;
|
||||
|
||||
interface RuleCache {
|
||||
data: any[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface RuleResult {
|
||||
purchaseMode: string;
|
||||
purchaseSchool: string;
|
||||
bidTemplate: string;
|
||||
requiredFiles: { fieldName: string; fileTypeName: string; required: boolean }[];
|
||||
matchedRules: any[];
|
||||
}
|
||||
|
||||
const ruleCache = ref<RuleCache | null>(null);
|
||||
|
||||
export function usePurchaseRules() {
|
||||
const loading = ref(false);
|
||||
const rules = ref<any[]>([]);
|
||||
|
||||
const loadRules = async (forceRefresh = false) => {
|
||||
if (!forceRefresh && ruleCache.value) {
|
||||
const now = Date.now();
|
||||
if (now - ruleCache.value.timestamp < CACHE_EXPIRE_TIME) {
|
||||
rules.value = ruleCache.value.data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getEnabledRules();
|
||||
rules.value = res.data || [];
|
||||
ruleCache.value = {
|
||||
data: rules.value,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('加载采购规则失败', e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const evaluate = async (params: {
|
||||
budget: number;
|
||||
isCentralized?: string;
|
||||
isSpecial?: string;
|
||||
hasSupplier?: string;
|
||||
projectType?: string;
|
||||
}): Promise<RuleResult | null> => {
|
||||
try {
|
||||
const res = await evaluateRules(params);
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
console.error('评估规则失败', e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getThresholds = () => {
|
||||
const thresholds: Record<string, number> = {
|
||||
deptPurchase: 50000,
|
||||
feasibility: 300000,
|
||||
publicSelect: 400000,
|
||||
govPurchase: 1000000
|
||||
};
|
||||
|
||||
rules.value.forEach(rule => {
|
||||
if (rule.ruleCode === 'DEPT_PURCHASE_THRESHOLD' && rule.amountMax) {
|
||||
thresholds.deptPurchase = Number(rule.amountMax);
|
||||
}
|
||||
if (rule.ruleCode === 'FEASIBILITY_REPORT' && rule.amountMin) {
|
||||
thresholds.feasibility = Number(rule.amountMin);
|
||||
}
|
||||
if (rule.ruleCode === 'PUBLIC_BID_40W_100W' && rule.amountMin) {
|
||||
thresholds.publicSelect = Number(rule.amountMin);
|
||||
}
|
||||
if (rule.ruleCode === 'GOV_PURCHASE_THRESHOLD' && rule.amountMin) {
|
||||
thresholds.govPurchase = Number(rule.amountMin);
|
||||
}
|
||||
});
|
||||
|
||||
return thresholds;
|
||||
};
|
||||
|
||||
loadRules();
|
||||
|
||||
return {
|
||||
loading,
|
||||
rules,
|
||||
loadRules,
|
||||
evaluate,
|
||||
getThresholds
|
||||
};
|
||||
}
|
||||
|
||||
let globalRulesInstance: ReturnType<typeof usePurchaseRules> | null = null;
|
||||
|
||||
export function usePurchaseRulesSingleton() {
|
||||
if (!globalRulesInstance) {
|
||||
globalRulesInstance = usePurchaseRules();
|
||||
}
|
||||
return globalRulesInstance;
|
||||
}
|
||||
246
src/views/purchase/purchasingRuleConfig/form.vue
Normal file
246
src/views/purchase/purchasingRuleConfig/form.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="700px"
|
||||
:close-on-click-modal="false"
|
||||
draggable
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="120px"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规则编码" prop="ruleCode">
|
||||
<el-input v-model="form.ruleCode" placeholder="请输入规则编码" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规则名称" prop="ruleName">
|
||||
<el-input v-model="form.ruleName" placeholder="请输入规则名称" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="规则类型" prop="ruleType">
|
||||
<el-select v-model="form.ruleType" placeholder="请选择规则类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in ruleTypes"
|
||||
:key="item.code"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="优先级" prop="sortOrder">
|
||||
<el-input-number v-model="form.sortOrder" :min="1" :max="999" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="金额区间">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<el-input-number
|
||||
v-model="form.amountMin"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="金额下限"
|
||||
style="width: 200px;"
|
||||
/>
|
||||
<span>元</span>
|
||||
<span style="margin: 0 8px;">至</span>
|
||||
<el-input-number
|
||||
v-model="form.amountMax"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="金额上限"
|
||||
style="width: 200px;"
|
||||
/>
|
||||
<span>元</span>
|
||||
<span style="color: #909399; font-size: 12px;">(不填表示不限)</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="附加条件字段">
|
||||
<el-select v-model="form.conditionField" placeholder="请选择" clearable style="width: 100%">
|
||||
<el-option label="是否集采" value="isCentralized" />
|
||||
<el-option label="是否特殊情况" value="isSpecial" />
|
||||
<el-option label="是否有推荐供应商" value="hasSupplier" />
|
||||
<el-option label="项目类别" value="projectType" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="附加条件值">
|
||||
<el-input v-model="form.conditionValue" placeholder="如:0、1、A等" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="动作类型" prop="actionType">
|
||||
<el-select v-model="form.actionType" placeholder="请选择动作类型" style="width: 100%">
|
||||
<el-option label="设置字段值" value="SET_FIELD" />
|
||||
<el-option label="切换模板" value="SWITCH_TEMPLATE" />
|
||||
<el-option label="要求文件" value="REQUIRE_FILE" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="动作目标" prop="actionTarget">
|
||||
<el-select v-model="form.actionTarget" placeholder="请选择" allow-create filterable style="width: 100%">
|
||||
<el-option v-if="form.actionType === 'SET_FIELD'" label="采购形式" value="purchaseMode" />
|
||||
<el-option v-if="form.actionType === 'SET_FIELD'" label="学校采购方式" value="purchaseSchool" />
|
||||
<el-option v-if="form.actionType === 'SWITCH_TEMPLATE'" label="比选模板" value="bidTemplate" />
|
||||
<el-option v-if="form.actionType === 'REQUIRE_FILE'" label="可行性论证报告" value="feasibilityReport" />
|
||||
<el-option v-if="form.actionType === 'REQUIRE_FILE'" label="会议纪要" value="meetingMinutes" />
|
||||
<el-option v-if="form.actionType === 'REQUIRE_FILE'" label="政府采购意向表" value="governmentIntention" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="动作值">
|
||||
<el-input v-model="form.actionValue" placeholder="设置的值" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否启用">
|
||||
<el-radio-group v-model="form.isEnabled">
|
||||
<el-radio label="1">启用</el-radio>
|
||||
<el-radio label="0">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="规则描述">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入规则描述"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="form.remark" placeholder="请输入备注" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="submitLoading">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { getObj, addObj, putObj, getRuleTypes } from "/@/api/purchase/purchasingRuleConfig";
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const formRef = ref();
|
||||
const ruleTypes = ref<{ code: string; name: string }[]>([]);
|
||||
|
||||
const isEdit = ref(false);
|
||||
const dialogTitle = computed(() => isEdit.value ? '编辑规则' : '新增规则');
|
||||
|
||||
const form = reactive({
|
||||
id: '',
|
||||
ruleCode: '',
|
||||
ruleName: '',
|
||||
ruleType: '',
|
||||
amountMin: null as number | null,
|
||||
amountMax: null as number | null,
|
||||
conditionField: '',
|
||||
conditionValue: '',
|
||||
actionType: '',
|
||||
actionTarget: '',
|
||||
actionValue: '',
|
||||
description: '',
|
||||
sortOrder: 1,
|
||||
isEnabled: '1',
|
||||
remark: ''
|
||||
});
|
||||
|
||||
const rules = {
|
||||
ruleCode: [{ required: true, message: '请输入规则编码', trigger: 'blur' }],
|
||||
ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
ruleType: [{ required: true, message: '请选择规则类型', trigger: 'change' }],
|
||||
actionType: [{ required: true, message: '请选择动作类型', trigger: 'change' }],
|
||||
actionTarget: [{ required: true, message: '请输入动作目标', trigger: 'blur' }]
|
||||
};
|
||||
|
||||
const openDialog = async (id?: string) => {
|
||||
visible.value = true;
|
||||
isEdit.value = !!id;
|
||||
resetForm();
|
||||
|
||||
const res = await getRuleTypes();
|
||||
ruleTypes.value = res.data || [];
|
||||
|
||||
if (id) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response = await getObj(id);
|
||||
if (response.data) {
|
||||
Object.assign(form, response.data);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = '';
|
||||
form.ruleCode = '';
|
||||
form.ruleName = '';
|
||||
form.ruleType = '';
|
||||
form.amountMin = null;
|
||||
form.amountMax = null;
|
||||
form.conditionField = '';
|
||||
form.conditionValue = '';
|
||||
form.actionType = '';
|
||||
form.actionTarget = '';
|
||||
form.actionValue = '';
|
||||
form.description = '';
|
||||
form.sortOrder = 1;
|
||||
form.isEnabled = '1';
|
||||
form.remark = '';
|
||||
formRef.value?.resetFields();
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
await formRef.value?.validate();
|
||||
submitLoading.value = true;
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await putObj(form);
|
||||
useMessage().success('修改成功');
|
||||
} else {
|
||||
await addObj(form);
|
||||
useMessage().success('新增成功');
|
||||
}
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '操作失败');
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ openDialog });
|
||||
</script>
|
||||
169
src/views/purchase/purchasingRuleConfig/index.vue
Normal file
169
src/views/purchase/purchasingRuleConfig/index.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="layout-padding">
|
||||
<div class="layout-padding-auto layout-padding-view">
|
||||
|
||||
<el-row>
|
||||
<div class="mb8" style="width: 100%">
|
||||
<el-button
|
||||
icon="folder-add"
|
||||
type="primary"
|
||||
class="ml10"
|
||||
@click="formDialogRef.openDialog()"
|
||||
v-auth="'purchase_purchasingruleconfig_add'"
|
||||
>
|
||||
新增规则
|
||||
</el-button>
|
||||
<el-button
|
||||
plain
|
||||
:disabled="multiple"
|
||||
icon="Delete"
|
||||
type="primary"
|
||||
v-auth="'purchase_purchasingruleconfig_del'"
|
||||
@click="handleDelete(selectObjs)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
<right-toolbar
|
||||
v-model:showSearch="showSearch"
|
||||
class="ml10 mr20"
|
||||
style="float: right;"
|
||||
@queryTable="getDataList"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
<el-table
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
border
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
@selection-change="selectionChangHandle"
|
||||
>
|
||||
<el-table-column type="selection" width="40" align="center" />
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="ruleCode" label="规则编码" width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="ruleName" label="规则名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="ruleTypeName" label="规则类型" width="120" />
|
||||
<el-table-column label="金额区间" width="180">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.amountMin || scope.row.amountMax">
|
||||
{{ formatAmount(scope.row.amountMin) }} ~ {{ formatAmount(scope.row.amountMax) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="附加条件" width="150">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.conditionField">
|
||||
{{ getConditionLabel(scope.row.conditionField) }} = {{ scope.row.conditionValue }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="actionTypeName" label="动作类型" width="100" />
|
||||
<el-table-column prop="description" label="规则描述" min-width="250" show-overflow-tooltip />
|
||||
<el-table-column prop="sortOrder" label="优先级" width="80" align="center" />
|
||||
<el-table-column label="状态" width="80" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.isEnabled === '1' ? 'success' : 'danger'" size="small">
|
||||
{{ scope.row.isEnabled === '1' ? '启用' : '禁用' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
icon="edit-pen"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'purchase_purchasingruleconfig_edit'"
|
||||
@click="formDialogRef.openDialog(scope.row.id)"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
icon="delete"
|
||||
text
|
||||
type="primary"
|
||||
v-auth="'purchase_purchasingruleconfig_del'"
|
||||
@click="handleDelete([scope.row.id])"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
@size-change="sizeChangeHandle"
|
||||
@current-change="currentChangeHandle"
|
||||
v-bind="state.pagination"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="purchasingRuleConfig">
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { fetchList, delObjs } from "/@/api/purchase/purchasingRuleConfig";
|
||||
import { useMessage, useMessageBox } from "/@/hooks/message";
|
||||
|
||||
const FormDialog = defineAsyncComponent(() => import('./form.vue'));
|
||||
|
||||
const formDialogRef = ref();
|
||||
const queryRef = ref();
|
||||
const showSearch = ref(true);
|
||||
const selectObjs = ref([]) as any;
|
||||
const multiple = ref(true);
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
queryForm: {},
|
||||
pageList: fetchList
|
||||
});
|
||||
|
||||
const {
|
||||
getDataList,
|
||||
currentChangeHandle,
|
||||
sizeChangeHandle,
|
||||
tableStyle
|
||||
} = useTable(state);
|
||||
|
||||
const selectionChangHandle = (objs: { id: string }[]) => {
|
||||
selectObjs.value = objs.map(({ id }) => id);
|
||||
multiple.value = !objs.length;
|
||||
};
|
||||
|
||||
const handleDelete = async (ids: string[]) => {
|
||||
try {
|
||||
await useMessageBox().confirm('此操作将永久删除该规则,是否继续?');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await delObjs(ids);
|
||||
getDataList();
|
||||
useMessage().success('删除成功');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '删除失败');
|
||||
}
|
||||
};
|
||||
|
||||
const formatAmount = (amount: number | null) => {
|
||||
if (amount === null || amount === undefined) return '不限';
|
||||
return (amount / 10000).toFixed(0) + '万';
|
||||
};
|
||||
|
||||
const getConditionLabel = (field: string) => {
|
||||
const map: Record<string, string> = {
|
||||
isCentralized: '是否集采',
|
||||
isSpecial: '是否特殊',
|
||||
hasSupplier: '是否有推荐供应商',
|
||||
projectType: '项目类别'
|
||||
};
|
||||
return map[field] || field;
|
||||
};
|
||||
</script>
|
||||
@@ -121,11 +121,16 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb12">
|
||||
<el-col :span="24" class="mb12">
|
||||
<el-form-item label="采购内容" prop="projectContent">
|
||||
<el-input v-model="dataForm.projectContent" type="textarea" :rows="3" :maxlength="1000" show-word-limit placeholder="请输入采购内容(限制1000字)" clearable :disabled="flowFieldDisabled('projectContent')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.BUSINESS_NEGOTIATION)">
|
||||
<el-form-item label="洽谈理由" prop="negotiationReason" required>
|
||||
<el-input v-model="dataForm.negotiationReason" type="textarea" :rows="3" :maxlength="500" show-word-limit placeholder="请输入洽谈理由(限制500字)" clearable :disabled="flowFieldDisabled('negotiationReason')" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.BUSINESS_NEGOTIATION)">
|
||||
<el-form-item label="商务洽谈表" prop="businessNegotiationTable" required>
|
||||
<upload-file v-model="dataForm.businessNegotiationTable" :limit="1" :file-type="['doc', 'docx', 'pdf']" :data="{ fileType: FILE_TYPE_MAP.businessNegotiationTable }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="flowFieldDisabled('businessNegotiationTable')" />
|
||||
@@ -517,6 +522,7 @@ import { addObj, tempStore, getObj, editObj, getApplyFiles } from '/@/api/purcha
|
||||
import { getTree } from '/@/api/purchase/purchasingcategory';
|
||||
import { getDicts } from '/@/api/admin/dict';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { usePurchaseRulesSingleton } from '/@/hooks/usePurchaseRules';
|
||||
import UploadFile from '/@/components/Upload/index.vue';
|
||||
import other from '/@/utils/other';
|
||||
import { Document, Download, QuestionFilled } from '@element-plus/icons-vue';
|
||||
@@ -656,7 +662,8 @@ const dataForm = reactive({
|
||||
agentId: '',
|
||||
agentName: '',
|
||||
representorName:'',
|
||||
representorType: ''
|
||||
representorType: '',
|
||||
negotiationReason: ''
|
||||
});
|
||||
/** 查看时展示的招标文件列表(实施采购上传的 type=130) */
|
||||
const viewImplementPurchaseFiles = ref<{ id: string; fileTitle?: string; createTime?: string; remark?: string }[]>([]);
|
||||
@@ -714,11 +721,13 @@ Object.entries(FILE_TYPE_MAP).forEach(([field, type]) => {
|
||||
FILE_TYPE_TO_FIELDS[type].push(field);
|
||||
});
|
||||
|
||||
// 金额阈值常量(与后端 PurchaseConstants 保持一致)
|
||||
const BUDGET_DEPT_PURCHASE_THRESHOLD = 50000; // 部门自行采购上限(< 5 万)
|
||||
const BUDGET_FEASIBILITY_THRESHOLD = 300000; // 可行性论证/会议纪要起点(≥ 30 万)
|
||||
const BUDGET_PUBLIC_SELECT_THRESHOLD = 400000; // 公开比选起点(≥ 40 万)
|
||||
const BUDGET_GOV_PURCHASE_THRESHOLD = 1000000; // 政府采购起点(≥ 100 万)
|
||||
// 金额阈值(从规则配置动态获取,默认值与后端 PurchaseConstants 保持一致)
|
||||
const { rules: purchaseRules, getThresholds, evaluate: evaluatePurchaseRules } = usePurchaseRulesSingleton();
|
||||
|
||||
const BUDGET_DEPT_PURCHASE_THRESHOLD = computed(() => getThresholds().deptPurchase || 50000);
|
||||
const BUDGET_FEASIBILITY_THRESHOLD = computed(() => getThresholds().feasibility || 300000);
|
||||
const BUDGET_PUBLIC_SELECT_THRESHOLD = computed(() => getThresholds().publicSelect || 400000);
|
||||
const BUDGET_GOV_PURCHASE_THRESHOLD = computed(() => getThresholds().govPurchase || 1000000);
|
||||
|
||||
// 部门采购方式字典 value(与 DeptPurchaseTypeEnum 一致)
|
||||
const DEPT_PURCHASE_TYPE = {
|
||||
@@ -775,7 +784,7 @@ const isDeptPurchase = computed(() => {
|
||||
return !!(isSpecialNoValue && isCentralizedNoValue &&
|
||||
dataForm.isSpecial === isSpecialNoValue &&
|
||||
dataForm.isCentralized === isCentralizedNoValue &&
|
||||
dataForm.budget && dataForm.budget < 50000);
|
||||
dataForm.budget && dataForm.budget < BUDGET_DEPT_PURCHASE_THRESHOLD.value);
|
||||
});
|
||||
|
||||
// 是否为“委托采购中心采购”途径
|
||||
@@ -795,8 +804,8 @@ const showPurchaseDetailBlocks = computed(() => {
|
||||
const schoolUnifiedPurchaseFormDefault = computed(() => {
|
||||
if (isDeptPurchase.value || dataForm.budget == null) return null;
|
||||
const budget = Number(dataForm.budget);
|
||||
if (budget >= BUDGET_GOV_PURCHASE_THRESHOLD) return '1'; // 政府采购
|
||||
if (budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_GOV_PURCHASE_THRESHOLD) {
|
||||
if (budget >= BUDGET_GOV_PURCHASE_THRESHOLD.value) return '1'; // 政府采购
|
||||
if (budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_GOV_PURCHASE_THRESHOLD.value) {
|
||||
if (dataForm.isCentralized === '0') return '2'; // 集采=否 → 学校自主采购
|
||||
if (dataForm.isCentralized === '1') return '1'; // 政府集中采购 → 政府采购
|
||||
if (dataForm.isCentralized === '2') return '2'; // 学校集中采购 → 学校自主采购
|
||||
@@ -959,7 +968,7 @@ watch(
|
||||
const isAutoSelectPurchaseType = computed(() => {
|
||||
if (!dataForm.budget) return false;
|
||||
const budget = dataForm.budget;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isServiceCategory.value && isSpecialServiceCategory.value;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_PUBLIC_SELECT_THRESHOLD.value && isServiceCategory.value && isSpecialServiceCategory.value;
|
||||
});
|
||||
|
||||
// 判断是否显示自动邀请比选模版(5万<=金额<40万,服务类目,特殊服务类目)
|
||||
@@ -967,7 +976,7 @@ const showAutoInviteSelect = computed(() => {
|
||||
if (!isDeptPurchase.value) return false;
|
||||
if (!dataForm.budget) return false;
|
||||
const budget = dataForm.budget;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isServiceCategory.value && isSpecialServiceCategory.value;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_PUBLIC_SELECT_THRESHOLD.value && isServiceCategory.value && isSpecialServiceCategory.value;
|
||||
});
|
||||
|
||||
// 判断是否显示学校统一采购的自动邀请比选模版(5万<=金额<40万,服务类目,特殊服务类目)
|
||||
@@ -975,7 +984,7 @@ const showAutoInviteSelectSchool = computed(() => {
|
||||
if (isDeptPurchase.value) return false;
|
||||
if (!dataForm.budget) return false;
|
||||
const budget = dataForm.budget;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isSpecialServiceCategory.value;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_PUBLIC_SELECT_THRESHOLD.value && isSpecialServiceCategory.value;
|
||||
});
|
||||
|
||||
// 判断是否显示自动公开比选模版(40万<=金额<100万,特殊服务类目:isMallService=1、isProjectService=1)
|
||||
@@ -983,7 +992,7 @@ const showAutoPublicSelect = computed(() => {
|
||||
if (isDeptPurchase.value) return false;
|
||||
if (!dataForm.budget) return false;
|
||||
const budget = dataForm.budget;
|
||||
return budget >= BUDGET_PUBLIC_SELECT_THRESHOLD && budget < BUDGET_GOV_PURCHASE_THRESHOLD && isSpecialServiceCategory.value;
|
||||
return budget >= BUDGET_PUBLIC_SELECT_THRESHOLD.value && budget < BUDGET_GOV_PURCHASE_THRESHOLD.value && isSpecialServiceCategory.value;
|
||||
});
|
||||
|
||||
// 获取需求文件的 prop 名称(用于表单验证)
|
||||
@@ -1005,7 +1014,7 @@ const isAutoSelectPurchaseTypeUnion = computed(() => {
|
||||
if (isDeptPurchase.value) return false;
|
||||
if (!dataForm.budget) return false;
|
||||
const budget = dataForm.budget;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_PUBLIC_SELECT_THRESHOLD && isSpecialServiceCategory.value;
|
||||
return budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_PUBLIC_SELECT_THRESHOLD.value && isSpecialServiceCategory.value;
|
||||
});
|
||||
|
||||
// 监听品目编码、预算金额、采购类型及采购途径变化,自动设置/清空采购方式
|
||||
@@ -1217,6 +1226,20 @@ const dataRules = reactive({
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
negotiationReason: [
|
||||
{
|
||||
validator: (_rule: any, value: string, callback: (e?: Error) => void) => {
|
||||
if (isPurchaseType(DEPT_PURCHASE_TYPE.BUSINESS_NEGOTIATION)) {
|
||||
if (!value || String(value).trim() === '') {
|
||||
callback(new Error('采购方式为商务洽谈时,洽谈理由不能为空'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// 取消
|
||||
@@ -1298,6 +1321,7 @@ async function loadDetail(applyId: string | number) {
|
||||
agentName: detail.agentName ?? '',
|
||||
representorName: detail.representorName ?? '',
|
||||
representorType: detail.representorType ?? '',
|
||||
negotiationReason: detail.negotiationReason ?? '',
|
||||
});
|
||||
setCategoryCodePath();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user