feat: add report review publish flow
This commit is contained in:
@@ -256,6 +256,15 @@ const isTaskReadonly = computed(() => {
|
||||
if (!detail.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const reportStatus = detail.value.report_summary?.report_status || "";
|
||||
if (["draft", "pending_publish", "updated", "rejected"].includes(reportStatus)) {
|
||||
return false;
|
||||
}
|
||||
if (reportStatus === "published") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
["submitted", "completed"].includes(detail.value.task_info.status) ||
|
||||
(Boolean(detail.value.task_info.submitted_at) && Boolean(detail.value.report_summary))
|
||||
@@ -799,7 +808,7 @@ async function submitResult(action: "save" | "submit") {
|
||||
key_points: normalizedKeyPoints(),
|
||||
...(qrInput ? { qr_input: qrInput } : {}),
|
||||
});
|
||||
ElMessage.success(response.message || (action === "submit" ? "验真吊牌已绑定,报告已发布" : "结论已保存"));
|
||||
ElMessage.success(response.message || (action === "submit" ? "验真吊牌已绑定,报告待管理员发布" : "结论已保存"));
|
||||
await loadDetail(detail.value.task_info.id);
|
||||
await fetchTasks();
|
||||
} catch (error) {
|
||||
@@ -817,7 +826,7 @@ async function publishCurrentTaskWithMaterialTag(qrInput: string) {
|
||||
id: detail.value.task_info.id,
|
||||
qr_input: qrInput,
|
||||
});
|
||||
ElMessage.success("验真吊牌已绑定,报告已发布");
|
||||
ElMessage.success("验真吊牌已绑定,报告待管理员发布");
|
||||
await loadDetail(detail.value.task_info.id);
|
||||
await fetchTasks();
|
||||
}
|
||||
@@ -835,7 +844,7 @@ async function bindMaterialTag() {
|
||||
materialTagInput.value = "";
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "验真吊牌绑定或报告发布失败");
|
||||
ElMessage.error(error?.message || "验真吊牌绑定失败");
|
||||
} finally {
|
||||
materialTagBinding.value = false;
|
||||
}
|
||||
@@ -843,12 +852,12 @@ async function bindMaterialTag() {
|
||||
|
||||
async function promptPublishMaterialTagInput() {
|
||||
try {
|
||||
const result = await ElMessageBox.prompt("是否已鉴定完成并确定发布报告?", "绑定验真吊牌并发布报告", {
|
||||
const result = await ElMessageBox.prompt("是否已鉴定完成并提交报告待发布?", "绑定验真吊牌并提交报告", {
|
||||
type: "warning",
|
||||
inputPlaceholder: "请扫描验真吊牌二维码",
|
||||
inputPattern: /\S+/,
|
||||
inputErrorMessage: "请扫描验真吊牌二维码",
|
||||
confirmButtonText: "是的,去绑定验真吊牌",
|
||||
confirmButtonText: "是的,绑定并提交",
|
||||
cancelButtonText: "取消",
|
||||
closeOnClickModal: false,
|
||||
});
|
||||
@@ -904,7 +913,7 @@ async function submitZhongjianReport() {
|
||||
report_files: zhongjianReportFiles.value,
|
||||
qr_input: qrInput,
|
||||
});
|
||||
ElMessage.success(response.message || "验真吊牌已绑定,报告已发布");
|
||||
ElMessage.success(response.message || "验真吊牌已绑定,报告待管理员发布");
|
||||
await loadDetail(detail.value.task_info.id);
|
||||
await fetchTasks();
|
||||
} catch (error: any) {
|
||||
@@ -1124,7 +1133,7 @@ onMounted(async () => {
|
||||
<el-button plain @click="openAssigneeDialog">分配处理人</el-button>
|
||||
<el-button v-if="canClaimTask" plain type="primary" :loading="assigneeSubmitting" @click="claimTask">认领给我</el-button>
|
||||
<el-button @click="resetZhongjianReportForm">重置内容</el-button>
|
||||
<el-button type="primary" :loading="zhongjianReportSubmitting" @click="submitZhongjianReport">提交并发布报告</el-button>
|
||||
<el-button type="primary" :loading="zhongjianReportSubmitting" @click="submitZhongjianReport">提交待发布报告</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button plain @click="returnToResultWorkbench">返回结论操作</el-button>
|
||||
@@ -1487,7 +1496,7 @@ onMounted(async () => {
|
||||
|
||||
<div class="task-form-block">
|
||||
<div class="task-form-block__title">吊牌绑定</div>
|
||||
<div class="task-panel__desc">提交结论或中检报告时扫描平台验真吊牌,绑定成功后发布报告。</div>
|
||||
<div class="task-panel__desc">提交结论或中检报告时扫描平台验真吊牌,绑定成功后进入待发布。</div>
|
||||
<div v-if="detail.material_tag" class="task-material-tag-bound">
|
||||
<div class="task-info-grid">
|
||||
<div class="task-info-item task-info-item--full">
|
||||
@@ -1672,7 +1681,7 @@ onMounted(async () => {
|
||||
<el-tab-pane v-if="isZhongjianTask" label="中检报告录入" name="zhongjian">
|
||||
<div :key="`zhongjian-${formRenderKey}`" class="task-form-stack">
|
||||
<el-alert
|
||||
title="请先在“填写结论”中补全物品信息、鉴定结论和模板项,再提交中检报告编号和文件;绑定吊牌成功后才会发布报告。"
|
||||
title="请先在“填写结论”中补全物品信息、鉴定结论和模板项,再提交中检报告编号和文件;绑定吊牌成功后报告进入待发布。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
|
||||
@@ -56,6 +56,7 @@ const drawerVisible = ref(false);
|
||||
const inspectionDrawerVisible = ref(false);
|
||||
const inspectionSubmitting = ref(false);
|
||||
const publishingId = ref<number | null>(null);
|
||||
const rejectingId = ref<number | null>(null);
|
||||
const traceVisibilitySavingId = ref<number | null>(null);
|
||||
const detailQrDataUrl = ref("");
|
||||
|
||||
@@ -69,6 +70,9 @@ const inspectionForm = ref<AdminManualInspectionPayload>(createInspectionPayload
|
||||
const route = useRoute();
|
||||
|
||||
const canPublishCurrentReport = computed(() => detail.value?.report_header.report_status === "pending_publish");
|
||||
const canRejectCurrentReport = computed(
|
||||
() => detail.value?.report_header.report_type !== "inspection" && detail.value?.report_header.report_status === "pending_publish",
|
||||
);
|
||||
const canEditCurrentInspection = computed(
|
||||
() => detail.value?.report_header.report_type === "inspection" && detail.value?.report_header.report_status !== "published",
|
||||
);
|
||||
@@ -84,6 +88,7 @@ const statusOptions = [
|
||||
{ label: "全部状态", value: "" },
|
||||
{ label: "已发布", value: "published" },
|
||||
{ label: "待发布", value: "pending_publish" },
|
||||
{ label: "已驳回", value: "rejected" },
|
||||
{ label: "草稿中", value: "draft" },
|
||||
{ label: "已更新", value: "updated" },
|
||||
{ label: "已作废", value: "invalid" },
|
||||
@@ -280,6 +285,11 @@ type PublishReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "r
|
||||
report_type: string;
|
||||
material_tag_bound: boolean;
|
||||
};
|
||||
type RejectReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "report_type"> | {
|
||||
id: number;
|
||||
report_status: string;
|
||||
report_type: string;
|
||||
};
|
||||
type ReportTraceVisibilityTarget = Pick<AdminReportListItem, "id" | "trace_info_visible">;
|
||||
|
||||
async function promptReportMaterialTagInput() {
|
||||
@@ -346,6 +356,62 @@ async function publishReport(row: PublishReportTarget) {
|
||||
}
|
||||
}
|
||||
|
||||
async function rejectReport(row: RejectReportTarget) {
|
||||
if (row.report_status !== "pending_publish") {
|
||||
ElMessage.warning("仅待发布报告可以驳回");
|
||||
return;
|
||||
}
|
||||
if (row.report_type === "inspection") {
|
||||
ElMessage.warning("补录检查单不支持驳回复鉴");
|
||||
return;
|
||||
}
|
||||
|
||||
let reason = "";
|
||||
try {
|
||||
const result = await ElMessageBox.prompt("请填写驳回原因,鉴定师将据此重新鉴定。", "驳回报告", {
|
||||
type: "warning",
|
||||
inputType: "textarea",
|
||||
inputPlaceholder: "例如:结论说明不完整、图片附件不清晰",
|
||||
inputPattern: /\S+/,
|
||||
inputErrorMessage: "请填写驳回原因",
|
||||
confirmButtonText: "确认驳回",
|
||||
cancelButtonText: "取消",
|
||||
closeOnClickModal: false,
|
||||
});
|
||||
reason = String(result.value || "").trim();
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
rejectingId.value = row.id;
|
||||
try {
|
||||
const response = await adminApi.rejectReport(row.id, reason);
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.message || "报告驳回失败");
|
||||
return;
|
||||
}
|
||||
|
||||
ElMessage.success(response.message || "报告已驳回");
|
||||
await fetchReports();
|
||||
if (drawerVisible.value && detail.value?.report_header.id === row.id) {
|
||||
await loadDetail(row.id);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "报告驳回失败");
|
||||
} finally {
|
||||
rejectingId.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
function logSummary(data: Record<string, any>) {
|
||||
const status = data.report_status || "-";
|
||||
const version = data.report_version ? `v${data.report_version}` : "";
|
||||
const publishTime = data.publish_time ? ` · ${data.publish_time}` : "";
|
||||
const rejectReason = data.reject_reason || data.invalid_reason ? ` · ${data.reject_reason || data.invalid_reason}` : "";
|
||||
return [status, version].filter(Boolean).join(" / ") + publishTime + rejectReason;
|
||||
}
|
||||
|
||||
function switchValueToBoolean(value: unknown) {
|
||||
if (typeof value === "boolean") return value;
|
||||
if (typeof value === "number") return value === 1;
|
||||
@@ -522,7 +588,7 @@ watch(
|
||||
</el-table-column>
|
||||
<el-table-column prop="institution_name" label="出具机构" min-width="160" />
|
||||
<el-table-column prop="publish_time" label="发布时间" min-width="170" />
|
||||
<el-table-column label="操作" fixed="right" width="220">
|
||||
<el-table-column label="操作" fixed="right" width="280">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row)">查看详情</el-button>
|
||||
<el-button
|
||||
@@ -534,7 +600,7 @@ watch(
|
||||
编辑检查单
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.report_status === 'pending_publish'"
|
||||
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||||
link
|
||||
type="warning"
|
||||
:loading="publishingId === row.id"
|
||||
@@ -542,6 +608,15 @@ watch(
|
||||
>
|
||||
发布报告
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.report_type !== 'inspection' && row.report_status === 'pending_publish'"
|
||||
link
|
||||
type="danger"
|
||||
:loading="rejectingId === row.id"
|
||||
@click="rejectReport(row)"
|
||||
>
|
||||
驳回报告
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -566,6 +641,19 @@ watch(
|
||||
>
|
||||
发布报告
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="canRejectCurrentReport"
|
||||
type="danger"
|
||||
plain
|
||||
:loading="rejectingId === detail.report_header.id"
|
||||
@click="rejectReport({
|
||||
id: detail.report_header.id,
|
||||
report_status: detail.report_header.report_status,
|
||||
report_type: detail.report_header.report_type,
|
||||
})"
|
||||
>
|
||||
驳回报告
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
@@ -588,6 +676,14 @@ watch(
|
||||
<OrderStatusTag :status="detail.report_header.report_status_text" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="detail.report_header.reject_reason" class="detail-card__desc">
|
||||
<div class="detail-label">驳回原因</div>
|
||||
<div class="detail-value">{{ detail.report_header.reject_reason }}</div>
|
||||
</div>
|
||||
<div v-if="detail.report_header.rejected_at" class="detail-card__desc">
|
||||
<div class="detail-label">驳回信息</div>
|
||||
<div class="detail-value">{{ detail.report_header.rejected_by_name || "-" }} / {{ detail.report_header.rejected_at }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">出具机构</div>
|
||||
<div class="detail-value">{{ detail.report_header.institution_name }}</div>
|
||||
@@ -634,6 +730,44 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card detail-card--wide">
|
||||
<div class="detail-card__title">审核记录</div>
|
||||
<div v-if="detail.audit_logs.length" class="report-log-list">
|
||||
<div v-for="log in detail.audit_logs" :key="`audit-${log.id}`" class="report-log-item">
|
||||
<div class="report-log-main">
|
||||
<span>{{ log.action_text }}</span>
|
||||
<span>{{ log.operator_name || "-" }}</span>
|
||||
<span>{{ log.created_at }}</span>
|
||||
</div>
|
||||
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||||
<div class="report-log-snapshot">{{ logSummary(log.after_data) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="detail-card__desc">
|
||||
<div class="detail-value">暂无审核记录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card detail-card--wide">
|
||||
<div class="detail-card__title">修改记录</div>
|
||||
<div v-if="detail.change_logs.length" class="report-log-list">
|
||||
<div v-for="log in detail.change_logs" :key="`change-${log.id}`" class="report-log-item">
|
||||
<div class="report-log-main">
|
||||
<span>{{ log.action_text }}</span>
|
||||
<span>{{ log.operator_name || "-" }}</span>
|
||||
<span>{{ log.created_at }}</span>
|
||||
</div>
|
||||
<div v-if="log.remark" class="report-log-remark">{{ log.remark }}</div>
|
||||
<div class="report-log-snapshot">
|
||||
{{ logSummary(log.before_data) }} -> {{ logSummary(log.after_data) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="detail-card__desc">
|
||||
<div class="detail-value">暂无修改记录</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">商品信息</div>
|
||||
<div class="detail-card__desc">
|
||||
@@ -1068,4 +1202,39 @@ watch(
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-card--wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.report-log-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.report-log-item {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 8px;
|
||||
background: #fffdfa;
|
||||
}
|
||||
|
||||
.report-log-main {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
color: var(--admin-text-main);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.report-log-remark,
|
||||
.report-log-snapshot {
|
||||
color: var(--admin-text-subtle);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user