更新
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,12 +121,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="mb12">
|
<el-col :span="24" class="mb12">
|
||||||
<el-form-item label="采购内容" prop="projectContent">
|
<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-input v-model="dataForm.projectContent" type="textarea" :rows="3" :maxlength="1000" show-word-limit placeholder="请输入采购内容(限制1000字)" clearable :disabled="flowFieldDisabled('projectContent')" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8" class="mb12" v-if="isPurchaseType(DEPT_PURCHASE_TYPE.BUSINESS_NEGOTIATION)">
|
<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>
|
<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')" />
|
<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')" />
|
||||||
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('business_negotiation')" style="margin-top: 8px; display: inline-block">下载商务洽谈表模版</el-button>
|
<el-button type="primary" link icon="Download" size="small" @click="downloadTemplate('business_negotiation')" style="margin-top: 8px; display: inline-block">下载商务洽谈表模版</el-button>
|
||||||
@@ -517,6 +522,7 @@ import { addObj, tempStore, getObj, editObj, getApplyFiles } from '/@/api/purcha
|
|||||||
import { getTree } from '/@/api/purchase/purchasingcategory';
|
import { getTree } from '/@/api/purchase/purchasingcategory';
|
||||||
import { getDicts } from '/@/api/admin/dict';
|
import { getDicts } from '/@/api/admin/dict';
|
||||||
import { useMessage } from '/@/hooks/message';
|
import { useMessage } from '/@/hooks/message';
|
||||||
|
import { usePurchaseRulesSingleton } from '/@/hooks/usePurchaseRules';
|
||||||
import UploadFile from '/@/components/Upload/index.vue';
|
import UploadFile from '/@/components/Upload/index.vue';
|
||||||
import other from '/@/utils/other';
|
import other from '/@/utils/other';
|
||||||
import { Document, Download, QuestionFilled } from '@element-plus/icons-vue';
|
import { Document, Download, QuestionFilled } from '@element-plus/icons-vue';
|
||||||
@@ -656,7 +662,8 @@ const dataForm = reactive({
|
|||||||
agentId: '',
|
agentId: '',
|
||||||
agentName: '',
|
agentName: '',
|
||||||
representorName:'',
|
representorName:'',
|
||||||
representorType: ''
|
representorType: '',
|
||||||
|
negotiationReason: ''
|
||||||
});
|
});
|
||||||
/** 查看时展示的招标文件列表(实施采购上传的 type=130) */
|
/** 查看时展示的招标文件列表(实施采购上传的 type=130) */
|
||||||
const viewImplementPurchaseFiles = ref<{ id: string; fileTitle?: string; createTime?: string; remark?: string }[]>([]);
|
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);
|
FILE_TYPE_TO_FIELDS[type].push(field);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 金额阈值常量(与后端 PurchaseConstants 保持一致)
|
// 金额阈值(从规则配置动态获取,默认值与后端 PurchaseConstants 保持一致)
|
||||||
const BUDGET_DEPT_PURCHASE_THRESHOLD = 50000; // 部门自行采购上限(< 5 万)
|
const { rules: purchaseRules, getThresholds, evaluate: evaluatePurchaseRules } = usePurchaseRulesSingleton();
|
||||||
const BUDGET_FEASIBILITY_THRESHOLD = 300000; // 可行性论证/会议纪要起点(≥ 30 万)
|
|
||||||
const BUDGET_PUBLIC_SELECT_THRESHOLD = 400000; // 公开比选起点(≥ 40 万)
|
const BUDGET_DEPT_PURCHASE_THRESHOLD = computed(() => getThresholds().deptPurchase || 50000);
|
||||||
const BUDGET_GOV_PURCHASE_THRESHOLD = 1000000; // 政府采购起点(≥ 100 万)
|
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 一致)
|
// 部门采购方式字典 value(与 DeptPurchaseTypeEnum 一致)
|
||||||
const DEPT_PURCHASE_TYPE = {
|
const DEPT_PURCHASE_TYPE = {
|
||||||
@@ -775,7 +784,7 @@ const isDeptPurchase = computed(() => {
|
|||||||
return !!(isSpecialNoValue && isCentralizedNoValue &&
|
return !!(isSpecialNoValue && isCentralizedNoValue &&
|
||||||
dataForm.isSpecial === isSpecialNoValue &&
|
dataForm.isSpecial === isSpecialNoValue &&
|
||||||
dataForm.isCentralized === isCentralizedNoValue &&
|
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(() => {
|
const schoolUnifiedPurchaseFormDefault = computed(() => {
|
||||||
if (isDeptPurchase.value || dataForm.budget == null) return null;
|
if (isDeptPurchase.value || dataForm.budget == null) return null;
|
||||||
const budget = Number(dataForm.budget);
|
const budget = Number(dataForm.budget);
|
||||||
if (budget >= BUDGET_GOV_PURCHASE_THRESHOLD) return '1'; // 政府采购
|
if (budget >= BUDGET_GOV_PURCHASE_THRESHOLD.value) return '1'; // 政府采购
|
||||||
if (budget >= BUDGET_DEPT_PURCHASE_THRESHOLD && budget < BUDGET_GOV_PURCHASE_THRESHOLD) {
|
if (budget >= BUDGET_DEPT_PURCHASE_THRESHOLD.value && budget < BUDGET_GOV_PURCHASE_THRESHOLD.value) {
|
||||||
if (dataForm.isCentralized === '0') return '2'; // 集采=否 → 学校自主采购
|
if (dataForm.isCentralized === '0') return '2'; // 集采=否 → 学校自主采购
|
||||||
if (dataForm.isCentralized === '1') return '1'; // 政府集中采购 → 政府采购
|
if (dataForm.isCentralized === '1') return '1'; // 政府集中采购 → 政府采购
|
||||||
if (dataForm.isCentralized === '2') return '2'; // 学校集中采购 → 学校自主采购
|
if (dataForm.isCentralized === '2') return '2'; // 学校集中采购 → 学校自主采购
|
||||||
@@ -959,7 +968,7 @@ watch(
|
|||||||
const isAutoSelectPurchaseType = computed(() => {
|
const isAutoSelectPurchaseType = computed(() => {
|
||||||
if (!dataForm.budget) return false;
|
if (!dataForm.budget) return false;
|
||||||
const budget = dataForm.budget;
|
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万,服务类目,特殊服务类目)
|
// 判断是否显示自动邀请比选模版(5万<=金额<40万,服务类目,特殊服务类目)
|
||||||
@@ -967,7 +976,7 @@ const showAutoInviteSelect = computed(() => {
|
|||||||
if (!isDeptPurchase.value) return false;
|
if (!isDeptPurchase.value) return false;
|
||||||
if (!dataForm.budget) return false;
|
if (!dataForm.budget) return false;
|
||||||
const budget = dataForm.budget;
|
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万,服务类目,特殊服务类目)
|
// 判断是否显示学校统一采购的自动邀请比选模版(5万<=金额<40万,服务类目,特殊服务类目)
|
||||||
@@ -975,7 +984,7 @@ const showAutoInviteSelectSchool = computed(() => {
|
|||||||
if (isDeptPurchase.value) return false;
|
if (isDeptPurchase.value) return false;
|
||||||
if (!dataForm.budget) return false;
|
if (!dataForm.budget) return false;
|
||||||
const budget = dataForm.budget;
|
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)
|
// 判断是否显示自动公开比选模版(40万<=金额<100万,特殊服务类目:isMallService=1、isProjectService=1)
|
||||||
@@ -983,7 +992,7 @@ const showAutoPublicSelect = computed(() => {
|
|||||||
if (isDeptPurchase.value) return false;
|
if (isDeptPurchase.value) return false;
|
||||||
if (!dataForm.budget) return false;
|
if (!dataForm.budget) return false;
|
||||||
const budget = dataForm.budget;
|
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 名称(用于表单验证)
|
// 获取需求文件的 prop 名称(用于表单验证)
|
||||||
@@ -1005,7 +1014,7 @@ const isAutoSelectPurchaseTypeUnion = computed(() => {
|
|||||||
if (isDeptPurchase.value) return false;
|
if (isDeptPurchase.value) return false;
|
||||||
if (!dataForm.budget) return false;
|
if (!dataForm.budget) return false;
|
||||||
const budget = dataForm.budget;
|
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'
|
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 ?? '',
|
agentName: detail.agentName ?? '',
|
||||||
representorName: detail.representorName ?? '',
|
representorName: detail.representorName ?? '',
|
||||||
representorType: detail.representorType ?? '',
|
representorType: detail.representorType ?? '',
|
||||||
|
negotiationReason: detail.negotiationReason ?? '',
|
||||||
});
|
});
|
||||||
setCategoryCodePath();
|
setCategoryCodePath();
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user