227 lines
6.4 KiB
Vue
227 lines
6.4 KiB
Vue
<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>
|