feat: update appraisal return address and test packaging assets

This commit is contained in:
wushumin
2026-06-15 20:08:36 +08:00
parent fa267c4413
commit 9be60fbe17
23 changed files with 1806 additions and 393 deletions

View File

@@ -199,6 +199,12 @@ export interface AdminManualOrderMeta {
}>;
}
export interface AdminCatalogCategoryOption {
id: number;
name: string;
code: string;
}
export interface AdminOrderDetail {
order_info: AdminOrderListItem & {
can_mark_received: boolean;
@@ -541,6 +547,25 @@ export const adminApi = {
getManualOrderMeta() {
return request<AdminManualOrderMeta>("/api/admin/manual-order/meta");
},
async getAppCatalogCategories() {
const data = await request<{
category_entries: Array<{
category_id: number;
category_name: string;
category_code: string;
}>;
}>("/api/app/home/index");
const list: AdminCatalogCategoryOption[] = data.category_entries.map((item) => ({
id: item.category_id,
name: item.category_name,
code: item.category_code,
}));
return {
list,
};
},
getExpressCompanies(params: { enabled_only?: 0 | 1 } = { enabled_only: 1 }) {
return request<{ list: AdminExpressCompanyItem[]; default_company: string }>("/api/admin/express-companies", { params });
},

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, reactive, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import { adminApi, type AdminAppraisalTaskDetail, type AdminFileAsset } from "../../api/admin";
import { adminApi, type AdminAppraisalTaskDetail, type AdminCatalogCategoryOption, type AdminFileAsset } from "../../api/admin";
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
const loading = ref(false);
@@ -25,6 +25,7 @@ const externalRemark = ref("");
const internalRemark = ref("");
const zhongjianReportNo = ref("");
const productName = ref("");
const categoryId = ref(0);
const categoryName = ref("");
const brandName = ref("");
const color = ref("");
@@ -33,6 +34,8 @@ const serialNo = ref("");
const zhongjianFiles = ref<AdminFileAsset[]>([]);
const evidenceFiles = ref<AdminFileAsset[]>([]);
const activePreviewVideo = ref<AdminFileAsset | null>(null);
const catalogCategories = ref<AdminCatalogCategoryOption[]>([]);
const categoryLoading = ref(false);
const supplementForm = reactive({
reason: "",
deadline: "",
@@ -56,6 +59,13 @@ const internalTagNo = computed(() => detail.value?.task_info.internal_tag_no ||
const resultSummary = computed(() => detail.value?.result_info.result_text || "暂未填写");
const reportSummary = computed(() => detail.value?.report_summary?.report_no || "");
const hasBoundMaterialTag = computed(() => Boolean(detail.value?.material_tag?.id));
const categoryOptions = computed(() => catalogCategories.value);
const categoryPickerIndex = computed(() => Math.max(0, categoryOptions.value.findIndex((item) => item.id === categoryId.value)));
const categoryPickerLabel = computed(() => selectedCategoryName(categoryId.value) || categoryName.value.trim());
const categoryPickerPlaceholder = computed(() => {
if (categoryLoading.value) return "正在加载品类";
return categoryOptions.value.length ? "请选择品类" : "暂无可选品类";
});
type AppraisalTemplate = NonNullable<AdminAppraisalTaskDetail["appraisal_template"]>;
function hasConditionFields(template?: AppraisalTemplate | null) {
@@ -74,6 +84,58 @@ function formatMoneyInput(value: string | number) {
return Number.isFinite(num) ? num : 0;
}
function selectedCategoryName(selectedCategoryId: number) {
return categoryOptions.value.find((item) => item.id === selectedCategoryId)?.name || "";
}
function resolveCategoryId(selectedCategoryId: number, selectedCategoryNameText: string) {
if (selectedCategoryId) return selectedCategoryId;
const categoryNameText = selectedCategoryNameText.trim();
return categoryOptions.value.find((item) => item.name === categoryNameText)?.id || 0;
}
function syncCurrentCategory() {
const resolvedCategoryId = resolveCategoryId(categoryId.value, categoryName.value);
categoryId.value = resolvedCategoryId;
categoryName.value = selectedCategoryName(resolvedCategoryId) || categoryName.value;
}
async function fetchCatalogMeta() {
if (catalogCategories.value.length || categoryLoading.value) return;
categoryLoading.value = true;
try {
const data = await adminApi.getAppCatalogCategories();
catalogCategories.value = data.list;
syncCurrentCategory();
} catch (error) {
showErrorToast(error, "品类列表加载失败");
} finally {
categoryLoading.value = false;
}
}
function onCategoryChange(event: any) {
if (isTaskReadonly.value) return;
const index = Number(event.detail?.value || 0);
const category = categoryOptions.value[index];
if (!category) return;
categoryId.value = category.id;
categoryName.value = category.name;
}
function productInfoPayload() {
const resolvedCategoryId = resolveCategoryId(categoryId.value, categoryName.value);
return {
category_id: resolvedCategoryId,
product_name: productName.value.trim(),
category_name: selectedCategoryName(resolvedCategoryId) || categoryName.value.trim(),
brand_name: brandName.value.trim(),
color: color.value.trim(),
size_spec: sizeSpec.value.trim(),
serial_no: serialNo.value.trim(),
};
}
function hydrate(detailData: AdminAppraisalTaskDetail) {
detail.value = detailData;
activeSection.value = detailData.task_info.service_provider === "zhongjian"
@@ -102,7 +164,9 @@ function hydrate(detailData: AdminAppraisalTaskDetail) {
internalRemark.value = detailData.result_info.internal_remark || "";
zhongjianReportNo.value = detailData.zhongjian_report?.report_no || "";
productName.value = detailData.product_info.product_name || "";
categoryId.value = resolveCategoryId(detailData.product_info.category_id, detailData.product_info.category_name || "");
categoryName.value = detailData.product_info.category_name || "";
syncCurrentCategory();
brandName.value = detailData.product_info.brand_name || "";
color.value = detailData.product_info.color || "";
sizeSpec.value = detailData.product_info.size_spec || "";
@@ -556,15 +620,7 @@ async function submitResult(action: "save" | "submit") {
adminApi.saveAppraisalTaskResult({
id: detail.value!.task_info.id,
action,
product_info: {
category_id: detail.value!.product_info.category_id,
product_name: productName.value.trim(),
category_name: categoryName.value.trim(),
brand_name: brandName.value.trim(),
color: color.value.trim(),
size_spec: sizeSpec.value.trim(),
serial_no: serialNo.value.trim(),
},
product_info: productInfoPayload(),
result_text: resultText.value.trim(),
result_desc: resultDesc.value.trim(),
...conditionPayload,
@@ -662,15 +718,7 @@ async function submitZhongjianReport() {
await adminApi.saveZhongjianAppraisalReport({
id: detail.value.task_info.id,
zhongjian_report_no: zhongjianReportNo.value.trim(),
product_info: {
category_id: detail.value.product_info.category_id,
product_name: productName.value.trim(),
category_name: categoryName.value.trim(),
brand_name: brandName.value.trim(),
color: color.value.trim(),
size_spec: sizeSpec.value.trim(),
serial_no: serialNo.value.trim(),
},
product_info: productInfoPayload(),
result_text: resultText.value.trim(),
result_desc: resultDesc.value.trim(),
attachments: evidenceFiles.value,
@@ -700,8 +748,11 @@ onLoad((options) => {
});
onShow(() => {
if (taskId.value && !pageReady.value) {
void fetchDetail();
if (taskId.value) {
void fetchCatalogMeta();
if (!pageReady.value) {
void fetchDetail();
}
}
});
</script>
@@ -767,7 +818,19 @@ onShow(() => {
<view class="stack" style="margin-top: 18rpx">
<view class="card-desc">报告展示信息</view>
<input v-model="productName" class="field" :disabled="isTaskReadonly" placeholder="产品名称" />
<input v-model="categoryName" class="field" :disabled="isTaskReadonly" placeholder="品类" />
<picker
:range="categoryOptions"
range-key="name"
:value="categoryPickerIndex"
:disabled="isTaskReadonly || categoryLoading || !categoryOptions.length"
@change="onCategoryChange"
>
<view class="field picker-field" :class="{ 'picker-field--disabled': isTaskReadonly }">
<text v-if="categoryPickerLabel" class="picker-field__value">{{ categoryPickerLabel }}</text>
<text v-else class="picker-field__placeholder">{{ categoryPickerPlaceholder }}</text>
<text v-if="!isTaskReadonly" class="picker-field__arrow"></text>
</view>
</picker>
<input v-model="brandName" class="field" :disabled="isTaskReadonly" placeholder="品牌" />
<view class="meta-grid">
<input v-model="color" class="field" :disabled="isTaskReadonly" placeholder="颜色" />
@@ -876,7 +939,19 @@ onShow(() => {
<view class="stack" style="margin-top: 18rpx">
<view class="card-desc">报告展示信息</view>
<input v-model="productName" class="field" :disabled="isTaskReadonly" placeholder="产品名称" />
<input v-model="categoryName" class="field" :disabled="isTaskReadonly" placeholder="品类" />
<picker
:range="categoryOptions"
range-key="name"
:value="categoryPickerIndex"
:disabled="isTaskReadonly || categoryLoading || !categoryOptions.length"
@change="onCategoryChange"
>
<view class="field picker-field" :class="{ 'picker-field--disabled': isTaskReadonly }">
<text v-if="categoryPickerLabel" class="picker-field__value">{{ categoryPickerLabel }}</text>
<text v-else class="picker-field__placeholder">{{ categoryPickerPlaceholder }}</text>
<text v-if="!isTaskReadonly" class="picker-field__arrow"></text>
</view>
</picker>
<input v-model="brandName" class="field" :disabled="isTaskReadonly" placeholder="品牌" />
<view class="meta-grid">
<input v-model="color" class="field" :disabled="isTaskReadonly" placeholder="颜色" />
@@ -1051,6 +1126,42 @@ onShow(() => {
font-weight: 800;
}
.picker-field {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16rpx;
}
.picker-field--disabled {
opacity: 0.82;
}
.picker-field__value,
.picker-field__placeholder {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.picker-field__value {
color: var(--work-text);
}
.picker-field__placeholder {
color: var(--work-text-muted);
}
.picker-field__arrow {
width: 14rpx;
height: 14rpx;
flex: 0 0 14rpx;
border-right: 3rpx solid var(--work-text-soft);
border-bottom: 3rpx solid var(--work-text-soft);
transform: rotate(45deg);
}
.attachment-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));