chore: prepare release build
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
|
||||
import { ElMessage, type InputInstance } from "element-plus";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import {
|
||||
adminApi,
|
||||
type AdminFileAsset,
|
||||
@@ -27,10 +27,6 @@ const evidenceUploading = ref(false);
|
||||
const appraisalTemplateLoading = ref(false);
|
||||
const transferTagNo = ref("");
|
||||
const transferScanLoading = ref(false);
|
||||
const publishDialogVisible = ref(false);
|
||||
const publishMaterialTagInput = ref("");
|
||||
const publishMaterialTagInputRef = ref<InputInstance | null>(null);
|
||||
const publishMaterialTagSubmitting = ref(false);
|
||||
const zhongjianReportNo = ref("");
|
||||
const zhongjianReportFiles = ref<AdminFileAsset[]>([]);
|
||||
const zhongjianReportUploading = ref(false);
|
||||
@@ -277,14 +273,10 @@ const canBindMaterialTag = computed(() => {
|
||||
if (!detail.value?.report_summary) {
|
||||
return false;
|
||||
}
|
||||
if (detail.value.task_info.service_provider === "zhongjian") {
|
||||
return false;
|
||||
}
|
||||
return detail.value.report_summary.report_status !== "published" && !detail.value.material_tag;
|
||||
});
|
||||
|
||||
const isZhongjianTask = computed(() => detail.value?.task_info.service_provider === "zhongjian");
|
||||
const isPhysicalTask = computed(() => Boolean(detail.value) && !isZhongjianTask.value);
|
||||
const canRequestSupplement = computed(() => detail.value?.task_info.status !== "completed");
|
||||
const currentAdmin = computed(() => getAdminInfo());
|
||||
const canClaimTask = computed(() => {
|
||||
@@ -783,6 +775,13 @@ async function submitResult(action: "save" | "submit") {
|
||||
if (action === "submit" && !validateRequiredKeyPoints()) {
|
||||
return;
|
||||
}
|
||||
let qrInput = "";
|
||||
if (action === "submit") {
|
||||
qrInput = await promptPublishMaterialTagInput();
|
||||
if (!qrInput) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
resultSubmitting.value = true;
|
||||
try {
|
||||
const response = await adminApi.saveAppraisalTaskResult({
|
||||
@@ -792,16 +791,11 @@ async function submitResult(action: "save" | "submit") {
|
||||
...resultForm,
|
||||
attachments: resultAttachments.value,
|
||||
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();
|
||||
if (action === "submit") {
|
||||
publishMaterialTagInput.value = "";
|
||||
publishDialogVisible.value = true;
|
||||
await nextTick();
|
||||
publishMaterialTagInputRef.value?.focus();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error(action === "submit" ? "结论提交失败" : "结论保存失败");
|
||||
@@ -812,10 +806,6 @@ async function submitResult(action: "save" | "submit") {
|
||||
|
||||
async function publishCurrentTaskWithMaterialTag(qrInput: string) {
|
||||
if (!detail.value) return;
|
||||
if (!isPhysicalTask.value) {
|
||||
ElMessage.warning("中检订单不使用平台验真吊牌");
|
||||
return;
|
||||
}
|
||||
|
||||
await adminApi.publishAppraisalTaskWithMaterialTag({
|
||||
id: detail.value.task_info.id,
|
||||
@@ -845,32 +835,23 @@ async function bindMaterialTag() {
|
||||
}
|
||||
}
|
||||
|
||||
async function publishDialogMaterialTag() {
|
||||
const qrInput = publishMaterialTagInput.value.trim();
|
||||
if (!qrInput) {
|
||||
ElMessage.warning("请扫描验真吊牌二维码");
|
||||
return;
|
||||
}
|
||||
|
||||
publishMaterialTagSubmitting.value = true;
|
||||
async function promptPublishMaterialTagInput() {
|
||||
try {
|
||||
await publishCurrentTaskWithMaterialTag(qrInput);
|
||||
publishDialogVisible.value = false;
|
||||
publishMaterialTagInput.value = "";
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "验真吊牌绑定或报告发布失败");
|
||||
} finally {
|
||||
publishMaterialTagSubmitting.value = false;
|
||||
const result = await ElMessageBox.prompt("是否已鉴定完成并确定发布报告?", "绑定验真吊牌并发布报告", {
|
||||
type: "warning",
|
||||
inputPlaceholder: "请扫描验真吊牌二维码",
|
||||
inputPattern: /\S+/,
|
||||
inputErrorMessage: "请扫描验真吊牌二维码",
|
||||
confirmButtonText: "是的,去绑定验真吊牌",
|
||||
cancelButtonText: "取消",
|
||||
closeOnClickModal: false,
|
||||
});
|
||||
return String(result.value || "").trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function focusPublishMaterialTagInput() {
|
||||
nextTick(() => {
|
||||
publishMaterialTagInputRef.value?.focus();
|
||||
});
|
||||
}
|
||||
|
||||
async function submitZhongjianReport() {
|
||||
if (!detail.value) return;
|
||||
if (!isZhongjianTask.value) {
|
||||
@@ -885,6 +866,10 @@ async function submitZhongjianReport() {
|
||||
ElMessage.warning("请至少上传 1 个中检报告文件");
|
||||
return;
|
||||
}
|
||||
const qrInput = await promptPublishMaterialTagInput();
|
||||
if (!qrInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
zhongjianReportSubmitting.value = true;
|
||||
try {
|
||||
@@ -892,8 +877,9 @@ async function submitZhongjianReport() {
|
||||
id: detail.value.task_info.id,
|
||||
zhongjian_report_no: zhongjianReportNo.value.trim(),
|
||||
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) {
|
||||
@@ -1428,7 +1414,7 @@ onMounted(async () => {
|
||||
<div :key="`result-${formRenderKey}`" class="task-form-stack">
|
||||
<el-alert
|
||||
v-if="isZhongjianTask"
|
||||
title="中检订单不走平台验真吊牌流程,请切换到中检报告录入。"
|
||||
title="中检订单请在中检报告录入页提交,提交时同样需要绑定验真吊牌。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
@@ -1476,16 +1462,8 @@ onMounted(async () => {
|
||||
|
||||
<div class="task-form-block">
|
||||
<div class="task-form-block__title">吊牌绑定</div>
|
||||
<div class="task-panel__desc">实物鉴定提交结论后扫描平台验真吊牌,绑定后发布报告。</div>
|
||||
<el-alert
|
||||
v-if="isZhongjianTask"
|
||||
title="中检订单不使用本平台验真吊牌。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-top: 12px;"
|
||||
/>
|
||||
<div v-else-if="detail.material_tag" class="task-material-tag-bound">
|
||||
<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">
|
||||
<div class="task-info-item__label">二维码链接</div>
|
||||
@@ -1673,7 +1651,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
|
||||
@@ -1836,33 +1814,6 @@ onMounted(async () => {
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="publishDialogVisible"
|
||||
title="绑定验真吊牌并发布报告"
|
||||
width="560px"
|
||||
@opened="focusPublishMaterialTagInput"
|
||||
>
|
||||
<div class="publish-dialog-body">
|
||||
<el-alert
|
||||
title="请扫描物品验真吊牌二维码,回车后发布正式报告。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
/>
|
||||
<el-input
|
||||
ref="publishMaterialTagInputRef"
|
||||
v-model="publishMaterialTagInput"
|
||||
size="large"
|
||||
placeholder="扫描平台验真吊牌二维码"
|
||||
clearable
|
||||
@keyup.enter="publishDialogMaterialTag"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="publishDialogVisible = false">稍后处理</el-button>
|
||||
<el-button type="primary" :loading="publishMaterialTagSubmitting" @click="publishDialogMaterialTag">完成并发布报告</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -1895,11 +1846,6 @@ onMounted(async () => {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.publish-dialog-body {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
:deep(.task-detail-drawer .el-drawer__body) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { adminApi, type AdminOrderDetail, type AdminOrderListItem, type AdminOrderWarehouseOption } from "../../api/admin";
|
||||
import { adminApi, type AdminFileAsset, type AdminManualOrderCreatePayload, type AdminManualOrderMeta, type AdminOrderDetail, type AdminOrderListItem, type AdminOrderWarehouseOption } from "../../api/admin";
|
||||
import OrderStatusTag from "../../components/OrderStatusTag.vue";
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -18,6 +18,12 @@ const returnDialogVisible = ref(false);
|
||||
const returnSubmitting = ref(false);
|
||||
const returnExpressCompany = ref("");
|
||||
const returnTrackingNo = ref("");
|
||||
const manualDialogVisible = ref(false);
|
||||
const manualSubmitting = ref(false);
|
||||
const manualMetaLoading = ref(false);
|
||||
const manualUploading = ref(false);
|
||||
const manualMeta = ref<AdminManualOrderMeta>({ categories: [], brands: [] });
|
||||
const manualForm = ref<AdminManualOrderCreatePayload>(createManualOrderForm());
|
||||
|
||||
const keyword = ref("");
|
||||
const serviceProvider = ref("");
|
||||
@@ -48,6 +54,7 @@ const sourceChannelOptions = [
|
||||
{ label: "小程序", value: "mini_program" },
|
||||
{ label: "H5", value: "h5" },
|
||||
{ label: "大客户推送订单", value: "enterprise_push" },
|
||||
{ label: "后台补录订单", value: "manual_entry" },
|
||||
];
|
||||
|
||||
const usageStatusMap: Record<string, string> = {
|
||||
@@ -107,6 +114,52 @@ const logisticsActionText = computed(() => {
|
||||
const canSubmitReturnLogistics = computed(() => Boolean(detail.value?.order_info.can_submit_return_logistics));
|
||||
const returnLogisticsBlockReason = computed(() => detail.value?.order_info.return_logistics_block_reason || "");
|
||||
const canMarkReturnReceived = computed(() => Boolean(detail.value?.order_info.can_mark_return_received));
|
||||
const manualBrandOptions = computed(() => {
|
||||
const categoryId = manualForm.value.product_info.category_id;
|
||||
const provider = manualForm.value.service_provider;
|
||||
return manualMeta.value.brands.filter((item) => {
|
||||
const categoryMatched = !categoryId || !item.category_ids.length || item.category_ids.includes(categoryId);
|
||||
const providerMatched = !item.supported_service_types.length || item.supported_service_types.includes(provider);
|
||||
return categoryMatched && providerMatched;
|
||||
});
|
||||
});
|
||||
|
||||
function createManualOrderForm(): AdminManualOrderCreatePayload {
|
||||
return {
|
||||
service_provider: "anxinyan",
|
||||
product_info: {
|
||||
category_id: 0,
|
||||
brand_id: 0,
|
||||
product_name: "",
|
||||
color: "",
|
||||
size_spec: "",
|
||||
serial_no: "",
|
||||
},
|
||||
extra_info: {
|
||||
purchase_channel: "",
|
||||
purchase_price: 0,
|
||||
usage_status: "",
|
||||
condition_desc: "",
|
||||
remark: "",
|
||||
},
|
||||
return_address: {
|
||||
consignee: "",
|
||||
mobile: "",
|
||||
province: "",
|
||||
city: "",
|
||||
district: "",
|
||||
detail_address: "",
|
||||
},
|
||||
materials: [
|
||||
{
|
||||
item_code: "manual_initial",
|
||||
item_name: "补录资料",
|
||||
is_required: false,
|
||||
files: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchOrders() {
|
||||
loading.value = true;
|
||||
@@ -126,6 +179,82 @@ async function fetchOrders() {
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureManualMeta() {
|
||||
if (manualMeta.value.categories.length && manualMeta.value.brands.length) return;
|
||||
manualMetaLoading.value = true;
|
||||
try {
|
||||
const response = await adminApi.getManualOrderMeta();
|
||||
manualMeta.value = response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error("补录订单选项加载失败");
|
||||
} finally {
|
||||
manualMetaLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openManualDialog() {
|
||||
manualForm.value = createManualOrderForm();
|
||||
manualDialogVisible.value = true;
|
||||
await ensureManualMeta();
|
||||
}
|
||||
|
||||
function handleManualCategoryChange() {
|
||||
const selectedBrand = manualBrandOptions.value.find((item) => item.id === manualForm.value.product_info.brand_id);
|
||||
if (!selectedBrand) {
|
||||
manualForm.value.product_info.brand_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function validateManualForm() {
|
||||
const form = manualForm.value;
|
||||
if (!form.product_info.category_id || !form.product_info.brand_id || !form.product_info.product_name.trim()) {
|
||||
ElMessage.warning("请完整填写品类、品牌和商品名称");
|
||||
return false;
|
||||
}
|
||||
const address = form.return_address;
|
||||
if (!address.consignee.trim() || !address.mobile.trim() || !address.province.trim() || !address.city.trim() || !address.district.trim() || !address.detail_address.trim()) {
|
||||
ElMessage.warning("请完整填写寄回收件信息");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function uploadManualMaterial(options: { file: File }) {
|
||||
manualUploading.value = true;
|
||||
try {
|
||||
const response = await adminApi.uploadManualOrderFile(options.file);
|
||||
manualForm.value.materials[0].files.push(response.data);
|
||||
ElMessage.success("资料已上传");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error(error instanceof Error ? error.message : "资料上传失败");
|
||||
} finally {
|
||||
manualUploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeManualMaterial(file: AdminFileAsset) {
|
||||
manualForm.value.materials[0].files = manualForm.value.materials[0].files.filter((item) => item.file_url !== file.file_url);
|
||||
}
|
||||
|
||||
async function submitManualOrder() {
|
||||
if (!validateManualForm()) return;
|
||||
manualSubmitting.value = true;
|
||||
try {
|
||||
const payload: AdminManualOrderCreatePayload = JSON.parse(JSON.stringify(manualForm.value));
|
||||
const response = await adminApi.createManualOrder(payload);
|
||||
ElMessage.success(`补录订单已创建:${response.data.order_no} / ${response.data.appraisal_no}`);
|
||||
manualDialogVisible.value = false;
|
||||
await fetchOrders();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
ElMessage.error(error instanceof Error ? error.message : "补录订单创建失败");
|
||||
} finally {
|
||||
manualSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function openDetail(row: AdminOrderListItem) {
|
||||
detailLoading.value = true;
|
||||
drawerVisible.value = true;
|
||||
@@ -289,6 +418,7 @@ onMounted(fetchOrders);
|
||||
<el-option v-for="item in sourceChannelOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="fetchOrders">查询</el-button>
|
||||
<el-button @click="openManualDialog">补录订单</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
@@ -665,6 +795,124 @@ onMounted(fetchOrders);
|
||||
<el-button type="primary" :loading="returnSubmitting" :disabled="!canSubmitReturnLogistics" @click="submitReturnLogistics">确认登记</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="manualDialogVisible" title="补录订单" width="860px" destroy-on-close>
|
||||
<div v-loading="manualMetaLoading" class="manual-order-form">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="补录订单创建后为待入库状态"
|
||||
description="创建成功后,可在入库台使用订单号或鉴定单号匹配并绑定内部流转挂牌,不需要填写寄入快递单号。"
|
||||
/>
|
||||
|
||||
<div class="manual-section">
|
||||
<div class="manual-section__title">订单与商品</div>
|
||||
<el-form label-position="top">
|
||||
<div class="manual-grid">
|
||||
<el-form-item label="服务类型">
|
||||
<el-select v-model="manualForm.service_provider" style="width: 100%">
|
||||
<el-option label="实物鉴定" value="anxinyan" />
|
||||
<el-option label="中检鉴定" value="zhongjian" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="品类">
|
||||
<el-select v-model="manualForm.product_info.category_id" filterable style="width: 100%" @change="handleManualCategoryChange">
|
||||
<el-option v-for="item in manualMeta.categories" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌">
|
||||
<el-select v-model="manualForm.product_info.brand_id" filterable style="width: 100%">
|
||||
<el-option v-for="item in manualBrandOptions" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="商品名称">
|
||||
<el-input v-model="manualForm.product_info.product_name" placeholder="例如:Classic Flap 手袋" />
|
||||
</el-form-item>
|
||||
<el-form-item label="颜色">
|
||||
<el-input v-model="manualForm.product_info.color" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格 / 尺寸">
|
||||
<el-input v-model="manualForm.product_info.size_spec" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="序列号">
|
||||
<el-input v-model="manualForm.product_info.serial_no" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="购买渠道">
|
||||
<el-input v-model="manualForm.extra_info.purchase_channel" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="购买价格">
|
||||
<el-input-number v-model="manualForm.extra_info.purchase_price" :min="0" :precision="2" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="使用情况">
|
||||
<el-select v-model="manualForm.extra_info.usage_status" clearable style="width: 100%">
|
||||
<el-option label="全新未使用" value="new" />
|
||||
<el-option label="轻微使用痕迹" value="light_use" />
|
||||
<el-option label="长期使用" value="used" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="成色说明">
|
||||
<el-input v-model="manualForm.extra_info.condition_desc" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
<el-form-item label="内部备注">
|
||||
<el-input v-model="manualForm.extra_info.remark" type="textarea" :rows="3" placeholder="可选" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="manual-section">
|
||||
<div class="manual-section__title">寄回信息</div>
|
||||
<el-form label-position="top">
|
||||
<div class="manual-grid">
|
||||
<el-form-item label="收件人">
|
||||
<el-input v-model="manualForm.return_address.consignee" placeholder="用于匹配或创建用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="manualForm.return_address.mobile" placeholder="按手机号复用已有用户" />
|
||||
</el-form-item>
|
||||
<el-form-item label="省份">
|
||||
<el-input v-model="manualForm.return_address.province" placeholder="例如:广东省" />
|
||||
</el-form-item>
|
||||
<el-form-item label="城市">
|
||||
<el-input v-model="manualForm.return_address.city" placeholder="例如:深圳市" />
|
||||
</el-form-item>
|
||||
<el-form-item label="区县">
|
||||
<el-input v-model="manualForm.return_address.district" placeholder="例如:南山区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="详细地址">
|
||||
<el-input v-model="manualForm.return_address.detail_address" placeholder="街道、门牌号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="manual-section">
|
||||
<div class="manual-section__title">初始资料</div>
|
||||
<div class="manual-upload-head">
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:http-request="uploadManualMaterial"
|
||||
:disabled="manualUploading"
|
||||
multiple
|
||||
>
|
||||
<el-button :loading="manualUploading">上传图片/视频/PDF</el-button>
|
||||
</el-upload>
|
||||
<span class="manual-upload-hint">{{ manualForm.materials[0].files.length }} 个资料文件</span>
|
||||
</div>
|
||||
<div v-if="manualForm.materials[0].files.length" class="manual-file-list">
|
||||
<div v-for="file in manualForm.materials[0].files" :key="file.file_url" class="manual-file-item">
|
||||
<span>{{ file.name || file.file_url }}</span>
|
||||
<el-button link type="danger" @click="removeManualMaterial(file)">移除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="manualDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="manualSubmitting" :disabled="manualUploading || manualMetaLoading" @click="submitManualOrder">创建补录订单</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -789,6 +1037,65 @@ onMounted(fetchOrders);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.manual-order-form {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.manual-section {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.manual-section__title {
|
||||
color: var(--admin-text-main);
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.manual-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 0 18px;
|
||||
}
|
||||
|
||||
.manual-upload-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.manual-upload-hint {
|
||||
color: var(--admin-text-subtle);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.manual-file-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.manual-file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--admin-border);
|
||||
border-radius: 8px;
|
||||
background: #fffdfa;
|
||||
}
|
||||
|
||||
.manual-file-item span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--admin-text-main);
|
||||
font-size: 13px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
.order-detail-hero {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -810,5 +1117,9 @@ onMounted(fetchOrders);
|
||||
.order-detail-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.manual-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -273,25 +273,58 @@ async function openDetailFromRouteQuery() {
|
||||
await loadDetail(reportId);
|
||||
}
|
||||
|
||||
async function publishReport(row: Pick<AdminReportListItem, "id" | "report_status"> | { id: number; report_status: string }) {
|
||||
type PublishReportTarget = Pick<AdminReportListItem, "id" | "report_status" | "report_type" | "material_tag_bound"> | {
|
||||
id: number;
|
||||
report_status: string;
|
||||
report_type: string;
|
||||
material_tag_bound: boolean;
|
||||
};
|
||||
|
||||
async function promptReportMaterialTagInput() {
|
||||
try {
|
||||
const result = await ElMessageBox.prompt("是否已鉴定完成并确定发布报告?", "绑定验真吊牌并发布报告", {
|
||||
type: "warning",
|
||||
inputPlaceholder: "请扫描验真吊牌二维码",
|
||||
inputPattern: /\S+/,
|
||||
inputErrorMessage: "请扫描验真吊牌二维码",
|
||||
confirmButtonText: "是的,去绑定验真吊牌",
|
||||
cancelButtonText: "取消",
|
||||
closeOnClickModal: false,
|
||||
});
|
||||
return String(result.value || "").trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
async function publishReport(row: PublishReportTarget) {
|
||||
if (row.report_status !== "pending_publish") {
|
||||
ElMessage.warning("仅待发布报告可以执行发布");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm("发布后用户端将可查看正式报告并进行验真,是否继续?", "发布报告", {
|
||||
type: "warning",
|
||||
confirmButtonText: "确认发布",
|
||||
cancelButtonText: "取消",
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
const needMaterialTag = row.report_type !== "inspection" && !row.material_tag_bound;
|
||||
let qrInput = "";
|
||||
if (needMaterialTag) {
|
||||
qrInput = await promptReportMaterialTagInput();
|
||||
if (!qrInput) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await ElMessageBox.confirm("发布后用户端将可查看正式报告并进行验真,是否继续?", "发布报告", {
|
||||
type: "warning",
|
||||
confirmButtonText: "确认发布",
|
||||
cancelButtonText: "取消",
|
||||
});
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
publishingId.value = row.id;
|
||||
try {
|
||||
const response = await adminApi.publishReport(row.id);
|
||||
const response = await adminApi.publishReport(row.id, qrInput);
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.message || "报告发布失败");
|
||||
return;
|
||||
@@ -427,6 +460,12 @@ watch(
|
||||
<OrderStatusTag :status="row.report_status_text" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="验真吊牌" min-width="120">
|
||||
<template #default="{ row }">
|
||||
<OrderStatusTag v-if="row.report_type !== 'inspection'" :status="row.material_tag_bound ? '已绑定' : '未绑定'" />
|
||||
<span v-else class="detail-label">不适用</span>
|
||||
</template>
|
||||
</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">
|
||||
@@ -464,7 +503,12 @@ watch(
|
||||
v-if="canPublishCurrentReport"
|
||||
type="primary"
|
||||
:loading="publishingId === detail.report_header.id"
|
||||
@click="publishReport({ id: detail.report_header.id, report_status: detail.report_header.report_status })"
|
||||
@click="publishReport({
|
||||
id: detail.report_header.id,
|
||||
report_status: detail.report_header.report_status,
|
||||
report_type: detail.report_header.report_type,
|
||||
material_tag_bound: Boolean(detail.material_tag),
|
||||
})"
|
||||
>
|
||||
发布报告
|
||||
</el-button>
|
||||
@@ -496,6 +540,32 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">验真吊牌</div>
|
||||
<template v-if="detail.report_header.report_type === 'inspection'">
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-value">补录检查单不需要绑定验真吊牌</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="detail.material_tag">
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">二维码链接</div>
|
||||
<div class="detail-value" style="word-break: break-all;">{{ detail.material_tag.qr_url }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">验真编码</div>
|
||||
<div class="detail-value">{{ detail.material_tag.verify_code }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">绑定时间</div>
|
||||
<div class="detail-value">{{ detail.material_tag.bound_at || "-" }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<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">
|
||||
@@ -652,7 +722,7 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="detail.report_header.service_provider !== 'zhongjian'" class="detail-card" style="grid-column: 1 / -1">
|
||||
<div class="detail-card" style="grid-column: 1 / -1">
|
||||
<div class="detail-card__title">扫码与公开链接</div>
|
||||
<div style="display: grid; grid-template-columns: 220px 1fr; gap: 24px; align-items: start;">
|
||||
<div
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, h, nextTick, ref, type PropType } from "vue";
|
||||
import { ElMessage, type InputInstance } from "element-plus";
|
||||
import { adminApi, type AdminWarehouseWorkbenchContext } from "../../api/admin";
|
||||
import {
|
||||
adminApi,
|
||||
type AdminFileAsset,
|
||||
type AdminReportDetail,
|
||||
type AdminWarehouseWorkbenchContext,
|
||||
} from "../../api/admin";
|
||||
import OrderStatusTag from "../../components/OrderStatusTag.vue";
|
||||
|
||||
const activeMode = ref<"inbound" | "zhongjian" | "return">("inbound");
|
||||
@@ -15,17 +20,31 @@ const returnTagNo = ref("");
|
||||
const returnMaterialQr = ref("");
|
||||
const returnExpressCompany = ref("");
|
||||
const returnTrackingNo = ref("");
|
||||
const returnPackingAttachments = ref<AdminFileAsset[]>([]);
|
||||
|
||||
const inboundContext = ref<AdminWarehouseWorkbenchContext | null>(null);
|
||||
const zhongjianContext = ref<AdminWarehouseWorkbenchContext | null>(null);
|
||||
const returnContext = ref<AdminWarehouseWorkbenchContext | null>(null);
|
||||
const returnReviewReport = ref<AdminReportDetail | null>(null);
|
||||
|
||||
const inboundTagInputRef = ref<InputInstance | null>(null);
|
||||
const returnMaterialInputRef = ref<InputInstance | null>(null);
|
||||
const returnTrackingInputRef = ref<InputInstance | null>(null);
|
||||
|
||||
const returnReviewDrawerVisible = ref(false);
|
||||
const returnReviewLoading = ref(false);
|
||||
const returnConfirmLoading = ref(false);
|
||||
const returnPackingUploading = ref(false);
|
||||
|
||||
const currentReturnIsZhongjian = computed(() => returnContext.value?.order_info.service_provider === "zhongjian");
|
||||
const returnConfirmed = computed(() => Boolean(returnContext.value?.transfer_flow?.return_confirmed_at));
|
||||
const returnMaterialMatched = computed(() => Boolean(returnContext.value?.return_verification?.verified));
|
||||
const returnReviewReportId = computed(() => Number(returnContext.value?.report_info?.id || returnContext.value?.return_verification?.report_id || 0));
|
||||
const returnReportActionText = computed(() => {
|
||||
if (returnConfirmed.value) return "报告已确认";
|
||||
if (currentReturnIsZhongjian.value || returnMaterialMatched.value) return "核对报告";
|
||||
return "匹配吊牌并核对报告";
|
||||
});
|
||||
|
||||
const OrderContextCard = defineComponent({
|
||||
name: "OrderContextCard",
|
||||
@@ -37,6 +56,23 @@ const OrderContextCard = defineComponent({
|
||||
},
|
||||
emits: ["open-file"],
|
||||
setup(props, { emit }) {
|
||||
const renderFileButtons = (title: string, files?: AdminFileAsset[]) => {
|
||||
if (!files?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return h("div", { class: "flow-log-files" }, [
|
||||
h("div", { class: "flow-log-files__title" }, title),
|
||||
h(
|
||||
"div",
|
||||
{ class: "file-list" },
|
||||
files.map((file) =>
|
||||
h("button", { class: "file-button", type: "button", onClick: () => emit("open-file", file.file_url) }, file.name || file.file_url),
|
||||
),
|
||||
),
|
||||
]);
|
||||
};
|
||||
|
||||
return () => {
|
||||
if (!props.context) {
|
||||
return h("div", { class: "detail-card empty-context" }, "等待扫码识别订单");
|
||||
@@ -97,6 +133,8 @@ const OrderContextCard = defineComponent({
|
||||
]),
|
||||
h("div", { class: "flow-log-item__meta" }, `${log.operator_name || "系统"} / ${log.after_stage || "-"} / ${log.after_location || "-"}`),
|
||||
log.remark ? h("div", { class: "flow-log-item__remark" }, log.remark) : null,
|
||||
renderFileButtons("入库附件", log.inbound_attachments),
|
||||
renderFileButtons("装箱附件", log.packing_attachments),
|
||||
]),
|
||||
),
|
||||
),
|
||||
@@ -114,7 +152,7 @@ function resetMode(mode: typeof activeMode.value) {
|
||||
async function lookupInbound() {
|
||||
const trackingNo = inboundTrackingNo.value.trim();
|
||||
if (!trackingNo) {
|
||||
ElMessage.warning("请扫描寄入运单号");
|
||||
ElMessage.warning("请扫描快递单号或输入鉴定订单号");
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
@@ -126,7 +164,7 @@ async function lookupInbound() {
|
||||
inboundTagInputRef.value?.focus();
|
||||
} catch (error: any) {
|
||||
inboundContext.value = null;
|
||||
ElMessage.error(error?.message || "未匹配到订单");
|
||||
ElMessage.error(error?.message || "未匹配到待入库订单");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -144,7 +182,7 @@ async function receiveInbound() {
|
||||
actionLoading.value = true;
|
||||
try {
|
||||
const response = await adminApi.receiveWarehouseInbound({
|
||||
tracking_no: inboundTrackingNo.value.trim(),
|
||||
inbound_no: inboundTrackingNo.value.trim(),
|
||||
internal_tag_no: inboundTagNo.value.trim(),
|
||||
});
|
||||
inboundContext.value = response.data;
|
||||
@@ -205,13 +243,19 @@ async function lookupReturn() {
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
returnMaterialQr.value = "";
|
||||
returnExpressCompany.value = "";
|
||||
returnTrackingNo.value = "";
|
||||
returnPackingAttachments.value = [];
|
||||
returnReviewReport.value = null;
|
||||
returnReviewDrawerVisible.value = false;
|
||||
const response = await adminApi.lookupWarehouseReturn(returnTagNo.value.trim());
|
||||
returnContext.value = response.data;
|
||||
ElMessage.success("已打开待寄回订单");
|
||||
await nextTick();
|
||||
if (response.data.order_info.service_provider === "zhongjian") {
|
||||
if (response.data.transfer_flow?.return_confirmed_at) {
|
||||
returnTrackingInputRef.value?.focus();
|
||||
} else {
|
||||
} else if (response.data.order_info.service_provider !== "zhongjian") {
|
||||
returnMaterialInputRef.value?.focus();
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -222,30 +266,119 @@ async function lookupReturn() {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmReturnReport() {
|
||||
async function openReturnReportReview() {
|
||||
const reportId = returnReviewReportId.value;
|
||||
if (!reportId) {
|
||||
ElMessage.warning("未找到可核对的报告");
|
||||
return;
|
||||
}
|
||||
|
||||
returnReviewDrawerVisible.value = true;
|
||||
returnReviewLoading.value = true;
|
||||
returnReviewReport.value = null;
|
||||
try {
|
||||
const response = await adminApi.getReportDetail(reportId);
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.message || "报告详情加载失败");
|
||||
return;
|
||||
}
|
||||
returnReviewReport.value = response.data;
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "报告详情加载失败");
|
||||
} finally {
|
||||
returnReviewLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleReturnReportStep() {
|
||||
if (!returnContext.value) {
|
||||
await lookupReturn();
|
||||
return;
|
||||
}
|
||||
actionLoading.value = true;
|
||||
try {
|
||||
const response = currentReturnIsZhongjian.value
|
||||
? await adminApi.confirmWarehouseReturnZhongjian(returnTagNo.value.trim())
|
||||
: await adminApi.verifyWarehouseReturnMaterialTag({
|
||||
internal_tag_no: returnTagNo.value.trim(),
|
||||
qr_input: returnMaterialQr.value.trim(),
|
||||
});
|
||||
returnContext.value = response.data;
|
||||
ElMessage.success(currentReturnIsZhongjian.value ? "中检报告已确认" : "验真吊牌已确认");
|
||||
if (returnConfirmed.value) {
|
||||
ElMessage.success("报告已确认,请填写回寄信息");
|
||||
await nextTick();
|
||||
returnTrackingInputRef.value?.focus();
|
||||
return;
|
||||
}
|
||||
if (!currentReturnIsZhongjian.value && !returnMaterialMatched.value && !returnMaterialQr.value.trim()) {
|
||||
ElMessage.warning("请扫描或填写平台验真吊牌链接");
|
||||
return;
|
||||
}
|
||||
|
||||
actionLoading.value = true;
|
||||
try {
|
||||
if (!currentReturnIsZhongjian.value && !returnMaterialMatched.value) {
|
||||
const response = await adminApi.verifyWarehouseReturnMaterialTag({
|
||||
internal_tag_no: returnTagNo.value.trim(),
|
||||
qr_input: returnMaterialQr.value.trim(),
|
||||
});
|
||||
returnContext.value = response.data;
|
||||
ElMessage.success(response.message || "验真吊牌匹配通过,请核对报告");
|
||||
}
|
||||
await openReturnReportReview();
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || "报告确认失败");
|
||||
ElMessage.error(error?.message || "报告核对失败");
|
||||
} finally {
|
||||
actionLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmReturnReview() {
|
||||
if (!returnReviewReport.value) {
|
||||
ElMessage.warning("请先加载报告详情");
|
||||
return;
|
||||
}
|
||||
returnConfirmLoading.value = true;
|
||||
try {
|
||||
const response = await adminApi.confirmWarehouseReturnReport({
|
||||
internal_tag_no: returnTagNo.value.trim(),
|
||||
report_id: returnReviewReport.value.report_header.id,
|
||||
});
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.message || "报告确认失败");
|
||||
return;
|
||||
}
|
||||
returnContext.value = response.data;
|
||||
returnReviewDrawerVisible.value = false;
|
||||
ElMessage.success(response.message || "报告已确认,可填写回寄运单");
|
||||
await nextTick();
|
||||
returnTrackingInputRef.value?.focus();
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "报告确认失败");
|
||||
} finally {
|
||||
returnConfirmLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadReturnPackingAttachment(options: { file: File }) {
|
||||
returnPackingUploading.value = true;
|
||||
try {
|
||||
const response = await adminApi.uploadWarehouseReturnPackingFile(options.file);
|
||||
if (response.code !== 0) {
|
||||
ElMessage.error(response.message || "装箱附件上传失败");
|
||||
return;
|
||||
}
|
||||
returnPackingAttachments.value.push(response.data);
|
||||
ElMessage.success("装箱附件已上传");
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error(error?.message || "装箱附件上传失败");
|
||||
} finally {
|
||||
returnPackingUploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function removeReturnPackingAttachment(fileUrl: string) {
|
||||
returnPackingAttachments.value = returnPackingAttachments.value.filter((item) => item.file_url !== fileUrl);
|
||||
}
|
||||
|
||||
function fileTypeText(file: AdminFileAsset) {
|
||||
return file.file_type === "image" ? "图片" : file.file_type === "video" ? "视频" : "附件";
|
||||
}
|
||||
|
||||
async function shipReturn() {
|
||||
if (!returnContext.value) {
|
||||
await lookupReturn();
|
||||
@@ -259,14 +392,20 @@ async function shipReturn() {
|
||||
ElMessage.warning("请填写回寄快递公司和运单号");
|
||||
return;
|
||||
}
|
||||
if (returnPackingUploading.value) {
|
||||
ElMessage.warning("装箱附件上传中,请稍后提交");
|
||||
return;
|
||||
}
|
||||
actionLoading.value = true;
|
||||
try {
|
||||
const response = await adminApi.shipWarehouseReturn({
|
||||
internal_tag_no: returnTagNo.value.trim(),
|
||||
express_company: returnExpressCompany.value.trim(),
|
||||
tracking_no: returnTrackingNo.value.trim(),
|
||||
packing_attachments: returnPackingAttachments.value,
|
||||
});
|
||||
returnContext.value = response.data;
|
||||
returnPackingAttachments.value = [];
|
||||
ElMessage.success("回寄运单已登记");
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.message || "回寄失败");
|
||||
@@ -299,7 +438,7 @@ function openFile(url: string) {
|
||||
<el-card class="panel-card" shadow="never">
|
||||
<template #header>入库扫描</template>
|
||||
<div class="scan-stack">
|
||||
<el-input v-model="inboundTrackingNo" size="large" placeholder="扫描寄入快递运单号" clearable @keyup.enter="lookupInbound" />
|
||||
<el-input v-model="inboundTrackingNo" size="large" placeholder="扫描快递单号 / 输入鉴定订单号" clearable @keyup.enter="lookupInbound" />
|
||||
<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>
|
||||
@@ -332,37 +471,215 @@ function openFile(url: string) {
|
||||
<div class="scan-stack">
|
||||
<el-input v-model="returnTagNo" size="large" placeholder="扫描内部流转码" clearable @keyup.enter="lookupReturn" />
|
||||
<el-input
|
||||
v-if="returnContext && !currentReturnIsZhongjian"
|
||||
v-if="returnContext && !currentReturnIsZhongjian && !returnMaterialMatched && !returnConfirmed"
|
||||
ref="returnMaterialInputRef"
|
||||
v-model="returnMaterialQr"
|
||||
size="large"
|
||||
placeholder="扫描平台验真吊牌"
|
||||
placeholder="扫描或填写平台验真吊牌链接"
|
||||
clearable
|
||||
@keyup.enter="confirmReturnReport"
|
||||
@keyup.enter="handleReturnReportStep"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="returnContext && currentReturnIsZhongjian"
|
||||
v-if="returnContext && currentReturnIsZhongjian && !returnConfirmed"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="中检订单不扫描平台验真吊牌"
|
||||
description="请核对中检报告编号和报告文件,确认无误后进入回寄物流填写。"
|
||||
description="请打开报告详情,核对中检报告编号和报告文件,确认无误后填写回寄物流。"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="returnContext && returnMaterialMatched && !returnConfirmed"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="验真吊牌已匹配当前订单报告"
|
||||
description="请继续核对报告详情,确认无误后填写回寄物流。"
|
||||
/>
|
||||
<el-alert
|
||||
v-if="returnContext && returnConfirmed"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="报告已确认"
|
||||
description="可填写回寄运单并上传打包装箱图片或视频。"
|
||||
/>
|
||||
<div class="actions-row">
|
||||
<el-button type="primary" :loading="loading" @click="lookupReturn">打开订单</el-button>
|
||||
<el-button type="success" :loading="actionLoading" :disabled="!returnContext" @click="confirmReturnReport">
|
||||
{{ currentReturnIsZhongjian ? "报告已确认" : "验真吊牌确认" }}
|
||||
<el-button
|
||||
v-if="returnContext && !returnConfirmed"
|
||||
type="success"
|
||||
:loading="actionLoading"
|
||||
:disabled="!returnContext"
|
||||
@click="handleReturnReportStep"
|
||||
>
|
||||
{{ returnReportActionText }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="returnContext" class="return-form">
|
||||
<div v-if="returnContext && returnConfirmed" class="return-form">
|
||||
<el-input v-model="returnExpressCompany" size="large" placeholder="回寄快递公司,例如:顺丰速运" />
|
||||
<el-input ref="returnTrackingInputRef" v-model="returnTrackingNo" size="large" placeholder="扫描或输入回寄运单号" @keyup.enter="shipReturn" />
|
||||
<el-button type="primary" size="large" :loading="actionLoading" :disabled="!returnConfirmed" @click="shipReturn">提交寄回</el-button>
|
||||
<div class="packing-upload">
|
||||
<div class="packing-upload-head">
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:http-request="uploadReturnPackingAttachment"
|
||||
:disabled="returnPackingUploading"
|
||||
accept="image/*,video/*"
|
||||
multiple
|
||||
>
|
||||
<el-button :loading="returnPackingUploading">上传装箱图片/视频</el-button>
|
||||
</el-upload>
|
||||
<span class="packing-upload-hint">{{ returnPackingAttachments.length }} 个装箱附件</span>
|
||||
</div>
|
||||
<div v-if="returnPackingAttachments.length" class="packing-file-list">
|
||||
<div v-for="file in returnPackingAttachments" :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="removeReturnPackingAttachment(file.file_url)">移除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="actionLoading"
|
||||
:disabled="returnPackingUploading || !returnExpressCompany.trim() || !returnTrackingNo.trim()"
|
||||
@click="shipReturn"
|
||||
>
|
||||
提交寄回
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<OrderContextCard :context="returnContext" @open-file="openFile" />
|
||||
</div>
|
||||
|
||||
<el-drawer v-model="returnReviewDrawerVisible" size="58%" title="回寄前报告核对">
|
||||
<div v-loading="returnReviewLoading" class="return-review">
|
||||
<template v-if="returnReviewReport">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="请核对报告编号、结论、附件和验真信息"
|
||||
description="确认无误后点击确认寄回,系统才会允许填写回寄运单。"
|
||||
/>
|
||||
|
||||
<div class="return-review-grid">
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">报告概览</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">报告编号</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.report_no }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">报告标题</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.report_title }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">报告状态</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.report_status_text }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">发布时间</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.publish_time || "-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">商品与结论</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">商品名称</div>
|
||||
<div class="detail-value">{{ returnReviewReport.product_info.product_name || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">品类 / 品牌</div>
|
||||
<div class="detail-value">{{ returnReviewReport.product_info.category_name || "-" }} / {{ returnReviewReport.product_info.brand_name || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">鉴定结论</div>
|
||||
<div class="detail-value">{{ returnReviewReport.result_info.result_text || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">结论说明</div>
|
||||
<div class="detail-value">{{ returnReviewReport.result_info.result_desc || "-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">验真信息</div>
|
||||
<template v-if="returnReviewReport.report_header.service_provider === 'zhongjian'">
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">中检报告编号</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.zhongjian_report_no || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">报告录入人</div>
|
||||
<div class="detail-value">{{ returnReviewReport.report_header.report_entry_admin_name || "-" }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">验真状态</div>
|
||||
<div class="detail-value">{{ returnReviewReport.verify_info.verify_status || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">验真链接</div>
|
||||
<div class="detail-value break-text">{{ returnReviewReport.verify_info.verify_url || "-" }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="detail-card">
|
||||
<div class="detail-card__title">估值与评级</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">成色评级</div>
|
||||
<div class="detail-value">{{ returnReviewReport.valuation_info.condition_grade || "-" }}</div>
|
||||
</div>
|
||||
<div class="detail-card__desc">
|
||||
<div class="detail-label">估值区间</div>
|
||||
<div class="detail-value">¥{{ returnReviewReport.valuation_info.valuation_min || 0 }} - ¥{{ returnReviewReport.valuation_info.valuation_max || 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-card return-review-files">
|
||||
<div class="detail-card__title">报告附件</div>
|
||||
<div v-if="returnReviewReport.evidence_attachments.length || returnReviewReport.zhongjian_report_files.length" class="file-list">
|
||||
<button
|
||||
v-for="file in returnReviewReport.evidence_attachments"
|
||||
:key="`evidence-${file.file_url}`"
|
||||
class="file-button"
|
||||
type="button"
|
||||
@click="openFile(file.file_url)"
|
||||
>
|
||||
{{ file.name || file.file_url }}
|
||||
</button>
|
||||
<button
|
||||
v-for="file in returnReviewReport.zhongjian_report_files"
|
||||
:key="`zhongjian-${file.file_url}`"
|
||||
class="file-button"
|
||||
type="button"
|
||||
@click="openFile(file.file_url)"
|
||||
>
|
||||
{{ file.name || file.file_url }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="detail-card__desc">
|
||||
<div class="detail-value">暂无报告附件</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="return-review-actions">
|
||||
<el-button @click="returnReviewDrawerVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="returnConfirmLoading" @click="confirmReturnReview">确认寄回</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-else description="暂无报告详情" />
|
||||
</div>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -397,6 +714,40 @@ function openFile(url: string) {
|
||||
border-top: 1px solid var(--admin-border);
|
||||
}
|
||||
|
||||
.packing-upload {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border: 1px dashed var(--admin-border);
|
||||
border-radius: 8px;
|
||||
background: #fffdfa;
|
||||
}
|
||||
|
||||
.packing-upload-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.packing-upload-hint,
|
||||
.packing-file-type {
|
||||
color: var(--admin-text-subtle);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.packing-file-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.packing-file-item {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-context {
|
||||
min-height: 260px;
|
||||
display: grid;
|
||||
@@ -541,4 +892,42 @@ function openFile(url: string) {
|
||||
.flow-log-item__remark {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.flow-log-files {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.flow-log-files__title {
|
||||
color: var(--admin-text-main);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.return-review {
|
||||
min-height: 260px;
|
||||
}
|
||||
|
||||
.return-review-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.return-review-files {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.return-review-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.break-text {
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user