chore: prepare release build

This commit is contained in:
wushumin
2026-05-16 16:32:56 +08:00
parent dd56e0861b
commit deecb5d33e
28 changed files with 4396 additions and 361 deletions

View File

@@ -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>