Files
yunzerwebsiteallinone/backend/src/views/apps/crm/bidding/bid/components/BidApproval.vue
T
2026-09-24 17:06:46 +08:00

227 lines
6.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>