增加招投标模块

This commit is contained in:
2026-09-24 17:06:46 +08:00
parent bc9e32d62a
commit eafa5e1cf4
51 changed files with 13453 additions and 5 deletions
@@ -0,0 +1,226 @@
<template>
<div class="bid-approval" v-loading="loading">
<div class="section-header">
<h3 class="section-title">立项审批</h3>
<el-button type="primary" :icon="Promotion" :disabled="disabled" @click="openSubmit">
提交立项审批
</el-button>
</div>
<el-empty v-if="list.length === 0" description="暂无审批记录" :image-size="80" />
<el-timeline v-else class="approval-timeline">
<el-timeline-item
v-for="row in list"
:key="row.id"
:type="statusType(row.approval_status)"
:timestamp="formatDateTime(row.submit_time)"
placement="top"
>
<el-card shadow="never" class="approval-card">
<div class="approval-row">
<el-tag :type="approvalStatusTag(row.approval_status)" size="small">
{{ approvalStatusText(row.approval_status) }}
</el-tag>
<span class="approval-type">{{ approvalTypeText(row.approval_type) }}</span>
<span class="approval-user">提交人:{{ row.submit_user_name || "-" }}</span>
</div>
<div v-if="row.approval_users" class="approval-row muted">
审批人:{{ row.approval_users }}
</div>
<div v-if="row.comment" class="approval-comment">{{ row.comment }}</div>
<div v-if="row.complete_time" class="approval-row muted">
完成时间:{{ formatDateTime(row.complete_time) }}
</div>
<div
v-if="row.approval_status === 1 && !disabled"
class="approval-actions"
>
<el-button type="success" size="small" @click="handleApprove(row, true)">通过</el-button>
<el-button type="danger" size="small" @click="handleApprove(row, false)">驳回</el-button>
</div>
</el-card>
</el-timeline-item>
</el-timeline>
<el-dialog v-model="dialogVisible" title="提交立项审批" width="560px" :close-on-click-modal="false">
<el-form :model="form" label-width="100px">
<el-form-item label="审批类型">
<el-select v-model="form.approval_type" style="width: 100%">
<el-option
v-for="opt in approvalTypeOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="审批人">
<el-input v-model="form.approval_users" placeholder="多个审批人用逗号分隔" />
</el-form-item>
<el-form-item label="说明">
<el-input v-model="form.comment" type="textarea" :rows="3" placeholder="立项说明(选填)" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Promotion } from "@element-plus/icons-vue";
import { getBidApprovalList, createBidApproval, approveBidApproval } from "@/api/crmBidding";
import {
approvalStatusText,
approvalStatusTag,
approvalTypeOptions,
formatDateTime,
} from "@/views/apps/crm/dict";
const props = defineProps({
bidId: { type: [Number, String], required: true },
disabled: { type: Boolean, default: false },
});
const emit = defineEmits(["count", "approved"]);
const loading = ref(false);
const list = ref([]);
const dialogVisible = ref(false);
const submitting = ref(false);
const form = reactive({ approval_type: "1", approval_users: "", comment: "" });
defineExpose({ reload: loadList });
onMounted(loadList);
async function loadList() {
if (!props.bidId) return;
loading.value = true;
try {
const res = await getBidApprovalList({ bid_id: props.bidId });
list.value = res?.data?.list || [];
emit("count", list.value.length);
} catch {
list.value = [];
emit("count", 0);
} finally {
loading.value = false;
}
}
function openSubmit() {
form.approval_type = "1";
form.approval_users = "";
form.comment = "";
dialogVisible.value = true;
}
async function handleSubmit() {
submitting.value = true;
try {
await createBidApproval({
bid_id: props.bidId,
approval_type: form.approval_type,
approval_users: form.approval_users,
comment: form.comment,
});
ElMessage.success("已提交,投标状态转为「立项审批中」");
dialogVisible.value = false;
loadList();
emit("approved");
} catch (e) {
ElMessage.error(e?.message || "提交失败");
} finally {
submitting.value = false;
}
}
async function handleApprove(row, pass) {
let comment = "";
try {
const { value } = await ElMessageBox.prompt(
pass ? "填写审批意见(可留空)" : "填写驳回原因",
pass ? "审批通过" : "审批驳回",
{ inputPlaceholder: "审批意见", inputType: "textarea" }
);
comment = value || "";
} catch {
return;
}
try {
await approveBidApproval(row.id, { pass, comment });
ElMessage.success(pass ? "已通过,投标转为「投标中」" : "已驳回,投标退回草稿");
loadList();
emit("approved");
} catch (e) {
ElMessage.error(e?.message || "操作失败");
}
}
function approvalTypeText(v) {
const hit = approvalTypeOptions.find((i) => String(i.value) === String(v));
return hit ? hit.label : "-";
}
function statusType(v) {
const map = { 1: "primary", 2: "success", 3: "danger" };
return map[Number(v)] || "info";
}
</script>
<style scoped>
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.section-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.approval-timeline {
padding-left: 8px;
}
.approval-card {
margin-bottom: 4px;
}
.approval-row {
display: flex;
align-items: center;
gap: 12px;
font-size: 13px;
margin-bottom: 4px;
}
.approval-row.muted {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.approval-type {
font-weight: 500;
}
.approval-user {
color: var(--el-text-color-secondary);
}
.approval-comment {
margin: 6px 0;
padding: 8px;
background: var(--el-bg-color-page);
border-radius: 4px;
font-size: 13px;
white-space: pre-wrap;
word-break: break-word;
}
.approval-actions {
margin-top: 8px;
display: flex;
gap: 8px;
}
</style>