chore: sync release updates

This commit is contained in:
wushumin
2026-05-22 15:47:23 +08:00
parent be64b8e5b7
commit baef2fb64c
23 changed files with 879 additions and 131 deletions

View File

@@ -1,3 +1,4 @@
import axios from "axios";
import request from "./request";
import type { AdminSessionInfo } from "../utils/auth";
@@ -21,6 +22,59 @@ export interface AdminFileAsset {
mime_type?: string;
}
export type AdminUploadScene = "appraisal_evidence" | "zhongjian_report" | "warehouse_inbound_evidence" | "warehouse_return_packing";
export interface AdminDirectUploadPolicy {
enabled: boolean;
upload_url?: string;
form_data?: Record<string, string | number>;
asset?: AdminFileAsset;
max_size?: number;
max_size_text?: string;
expires_at?: string;
}
async function uploadFileToOss(uploadUrl: string, formData: Record<string, string | number>, file: File) {
const ossFormData = new FormData();
Object.entries(formData).forEach(([key, value]) => {
ossFormData.append(key, String(value));
});
ossFormData.append("file", file);
await axios.post(uploadUrl, ossFormData, { timeout: 300000 });
}
async function uploadManagedAdminFile(file: File, scene: AdminUploadScene, fallbackUrl: string, fallbackFields: Record<string, string | number> = {}) {
const policyResponse = await request.post("/api/admin/file-upload/direct-policy", {
upload_scene: scene,
original_name: file.name,
file_size: file.size,
mime_type: file.type,
}) as { code: number; message: string; data: AdminDirectUploadPolicy };
const policy = policyResponse.data;
if (!policy.enabled) {
const formData = new FormData();
formData.append("file", file);
formData.append("upload_scene", scene);
Object.entries(fallbackFields).forEach(([key, value]) => {
formData.append(key, String(value));
});
return request.post(fallbackUrl, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}) as Promise<{ code: number; message: string; data: AdminFileAsset }>;
}
if (!policy.upload_url || !policy.form_data || !policy.asset) {
throw new Error("OSS 上传签名无效,请稍后重试");
}
await uploadFileToOss(policy.upload_url, policy.form_data, file);
return { code: 0, message: "ok", data: policy.asset };
}
export interface AdminTransferFlowSummary {
id: number;
internal_tag_no: string;
@@ -1800,24 +1854,12 @@ export const adminApi = {
};
}>;
},
uploadAppraisalEvidenceFile(file: File) {
const formData = new FormData();
formData.append("file", file);
return request.post("/api/admin/appraisal-task/evidence/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}) as Promise<{
uploadAppraisalEvidenceFile(file: File, scene: AdminUploadScene = "appraisal_evidence", taskId?: number) {
const fallbackFields: Record<string, string | number> = taskId ? { task_id: taskId } : {};
return uploadManagedAdminFile(file, scene, "/api/admin/appraisal-task/evidence/upload", fallbackFields) as Promise<{
code: number;
message: string;
data: {
file_id: string;
file_url: string;
thumbnail_url: string;
name?: string;
file_type?: string;
mime_type?: string;
};
data: AdminFileAsset;
}>;
},
deleteAppraisalEvidenceFile(fileUrl: string) {
@@ -1888,14 +1930,15 @@ export const adminApi = {
internal_tag_no: internalTagNo,
}) as Promise<{ code: number; message: string; data: AdminWarehouseWorkbenchContext }>;
},
uploadWarehouseInboundEvidenceFile(file: File) {
return uploadManagedAdminFile(file, "warehouse_inbound_evidence", "/api/admin/warehouse-workbench/inbound/evidence/upload") as Promise<{
code: number;
message: string;
data: AdminFileAsset;
}>;
},
uploadWarehouseReturnPackingFile(file: File) {
const formData = new FormData();
formData.append("file", file);
return request.post("/api/admin/warehouse-workbench/return/packing/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
}) as Promise<{
return uploadManagedAdminFile(file, "warehouse_return_packing", "/api/admin/warehouse-workbench/return/packing/upload") as Promise<{
code: number;
message: string;
data: AdminFileAsset;

View File

@@ -579,6 +579,9 @@ function triggerEvidenceUpload() {
}
async function handleEvidenceFileSelect(event: Event) {
if (!detail.value) {
return;
}
const target = event.target as HTMLInputElement;
const files = Array.from(target.files || []);
if (!files.length) {
@@ -588,7 +591,7 @@ async function handleEvidenceFileSelect(event: Event) {
evidenceUploading.value = true;
try {
for (const file of files) {
const response = await adminApi.uploadAppraisalEvidenceFile(file);
const response = await adminApi.uploadAppraisalEvidenceFile(file, "appraisal_evidence", detail.value.task_info.id);
resultAttachments.value.push(response.data);
}
ElMessage.success("附件上传成功");
@@ -609,6 +612,9 @@ function triggerZhongjianReportUpload() {
}
async function handleZhongjianReportFileSelect(event: Event) {
if (!detail.value) {
return;
}
const target = event.target as HTMLInputElement;
const files = Array.from(target.files || []);
if (!files.length) {
@@ -618,7 +624,7 @@ async function handleZhongjianReportFileSelect(event: Event) {
zhongjianReportUploading.value = true;
try {
for (const file of files) {
const response = await adminApi.uploadAppraisalEvidenceFile(file);
const response = await adminApi.uploadAppraisalEvidenceFile(file, "zhongjian_report", detail.value.task_info.id);
zhongjianReportFiles.value.push(response.data);
}
ElMessage.success("中检报告文件已上传");

View File

@@ -596,6 +596,16 @@ watch(
<div class="detail-label">说明</div>
<div class="detail-value">{{ detail.result_info.result_desc || "-" }}</div>
</div>
<template v-if="detail.result_info.key_points?.length">
<div v-for="(item, index) in detail.result_info.key_points" :key="`${item.point_code || item.point_name}-${index}`" class="detail-card__desc">
<div class="detail-label">{{ item.point_name || "鉴定项" }}</div>
<div class="detail-value">{{ [item.point_value, item.point_remark].filter(Boolean).join("") || "-" }}</div>
</div>
</template>
<div v-if="detail.result_info.external_remark" class="detail-card__desc">
<div class="detail-label">对外备注</div>
<div class="detail-value">{{ detail.result_info.external_remark }}</div>
</div>
</div>
<div class="detail-card">
@@ -634,6 +644,10 @@ watch(
<div class="detail-label">成色评级</div>
<div class="detail-value">{{ detail.valuation_info.condition_grade || "-" }}</div>
</div>
<div class="detail-card__desc">
<div class="detail-label">成色说明</div>
<div class="detail-value">{{ detail.valuation_info.condition_desc || "-" }}</div>
</div>
<div class="detail-card__desc">
<div class="detail-label">估值区间</div>
<div class="detail-value">¥{{ detail.valuation_info.valuation_min || 0 }} - ¥{{ detail.valuation_info.valuation_max || 0 }}</div>

View File

@@ -20,6 +20,7 @@ const returnTagNo = ref("");
const returnMaterialQr = ref("");
const returnExpressCompany = ref("");
const returnTrackingNo = ref("");
const inboundAttachments = ref<AdminFileAsset[]>([]);
const returnPackingAttachments = ref<AdminFileAsset[]>([]);
const inboundContext = ref<AdminWarehouseWorkbenchContext | null>(null);
@@ -34,6 +35,7 @@ const returnTrackingInputRef = ref<InputInstance | null>(null);
const returnReviewDrawerVisible = ref(false);
const returnReviewLoading = ref(false);
const returnConfirmLoading = ref(false);
const inboundUploading = ref(false);
const returnPackingUploading = ref(false);
const currentReturnIsZhongjian = computed(() => returnContext.value?.order_info.service_provider === "zhongjian");
@@ -157,6 +159,7 @@ async function lookupInbound() {
}
loading.value = true;
try {
inboundAttachments.value = [];
const response = await adminApi.lookupWarehouseInbound(trackingNo);
inboundContext.value = response.data;
ElMessage.success("已匹配订单");
@@ -179,13 +182,19 @@ async function receiveInbound() {
ElMessage.warning("请扫描内部流转挂牌");
return;
}
if (inboundUploading.value) {
ElMessage.warning("入库附件上传中,请稍后提交");
return;
}
actionLoading.value = true;
try {
const response = await adminApi.receiveWarehouseInbound({
inbound_no: inboundTrackingNo.value.trim(),
internal_tag_no: inboundTagNo.value.trim(),
inbound_attachments: inboundAttachments.value,
});
inboundContext.value = response.data;
inboundAttachments.value = [];
ElMessage.success("入库完成");
} catch (error: any) {
ElMessage.error(error?.message || "入库失败");
@@ -194,6 +203,28 @@ async function receiveInbound() {
}
}
async function uploadInboundAttachment(options: { file: File }) {
inboundUploading.value = true;
try {
const response = await adminApi.uploadWarehouseInboundEvidenceFile(options.file);
if (response.code !== 0) {
ElMessage.error(response.message || "入库附件上传失败");
return;
}
inboundAttachments.value.push(response.data);
ElMessage.success("入库附件已上传");
} catch (error: any) {
console.error(error);
ElMessage.error(error?.message || "入库附件上传失败");
} finally {
inboundUploading.value = false;
}
}
function removeInboundAttachment(fileUrl: string) {
inboundAttachments.value = inboundAttachments.value.filter((item) => item.file_url !== fileUrl);
}
async function lookupZhongjian() {
if (!zhongjianTagNo.value.trim()) {
ElMessage.warning("请扫描内部流转码");
@@ -442,7 +473,30 @@ function openFile(url: string) {
<el-input ref="inboundTagInputRef" v-model="inboundTagNo" size="large" placeholder="扫描内部流转挂牌" clearable @keyup.enter="receiveInbound" />
<div class="actions-row">
<el-button type="primary" :loading="loading" @click="lookupInbound">匹配订单</el-button>
<el-button type="success" :loading="actionLoading" :disabled="!inboundContext" @click="receiveInbound">绑定挂牌并入库</el-button>
<el-button type="success" :loading="actionLoading" :disabled="!inboundContext || inboundUploading" @click="receiveInbound">绑定挂牌并入库</el-button>
</div>
<div v-if="inboundContext" class="packing-upload">
<div class="packing-upload-head">
<el-upload
:show-file-list="false"
:http-request="uploadInboundAttachment"
:disabled="inboundUploading"
accept="image/*,video/*"
multiple
>
<el-button :loading="inboundUploading">上传拆包图片/视频</el-button>
</el-upload>
<span class="packing-upload-hint">{{ inboundAttachments.length }} 个入库附件</span>
</div>
<div v-if="inboundAttachments.length" class="packing-file-list">
<div v-for="file in inboundAttachments" :key="file.file_url" class="packing-file-item">
<button class="file-button" type="button" @click="openFile(file.file_url)">
{{ file.name || file.file_url }}
</button>
<span class="packing-file-type">{{ fileTypeText(file) }}</span>
<el-button link type="danger" @click="removeInboundAttachment(file.file_url)">移除</el-button>
</div>
</div>
</div>
</div>
</el-card>