Simplify manual order creation

This commit is contained in:
wushumin
2026-05-21 18:55:01 +08:00
parent d0c4332468
commit b98d6164a7
5 changed files with 49 additions and 233 deletions

View File

@@ -56,6 +56,7 @@ export interface AdminManualOrderCreatePayload {
product_info: {
category_id: number;
brand_id: number;
brand_name: string;
product_name: string;
color: string;
size_spec: string;

View File

@@ -1,43 +1,22 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app";
import { adminApi, type AdminFileAsset, type AdminManualOrderCreatePayload, type AdminManualOrderMeta } from "../../api/admin";
import { adminApi, type AdminManualOrderCreatePayload, type AdminManualOrderMeta } from "../../api/admin";
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
import { buildRegionPickerState, updateRegionPickerIndexes } from "../../utils/regions";
const loading = ref(false);
const submitting = ref(false);
const uploading = ref(false);
const meta = ref<AdminManualOrderMeta>({ categories: [], brands: [] });
const form = ref<AdminManualOrderCreatePayload>(createForm());
const purchasePriceInput = ref("");
const regionPickerIndexes = ref<[number, number, number]>([0, 0, 0]);
const providerOptions = [
{ label: "实物鉴定", value: "anxinyan" },
{ label: "中检鉴定", value: "zhongjian" },
];
const usageOptions = [
{ label: "未选择", value: "" },
{ label: "全新未使用", value: "new" },
{ label: "轻微使用痕迹", value: "light_use" },
{ label: "长期使用", value: "used" },
];
const providerIndex = computed(() => Math.max(0, providerOptions.findIndex((item) => item.value === form.value.service_provider)));
const categoryIndex = computed(() => Math.max(0, meta.value.categories.findIndex((item) => item.id === form.value.product_info.category_id)));
const brandOptions = computed(() => {
const categoryId = form.value.product_info.category_id;
const provider = form.value.service_provider;
return meta.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;
});
});
const brandIndex = computed(() => Math.max(0, brandOptions.value.findIndex((item) => item.id === form.value.product_info.brand_id)));
const usageIndex = computed(() => Math.max(0, usageOptions.findIndex((item) => item.value === form.value.extra_info.usage_status)));
const materialFiles = computed(() => form.value.materials[0].files);
const selectedRegionText = computed(() => {
const { province, city, district } = form.value.return_address;
return province && city && district ? `${province} / ${city} / ${district}` : "";
@@ -50,6 +29,7 @@ function createForm(): AdminManualOrderCreatePayload {
product_info: {
category_id: 0,
brand_id: 0,
brand_name: "",
product_name: "",
color: "",
size_spec: "",
@@ -88,9 +68,6 @@ async function fetchMeta() {
if (!form.value.product_info.category_id && meta.value.categories.length) {
form.value.product_info.category_id = meta.value.categories[0].id;
}
if (!form.value.product_info.brand_id && brandOptions.value.length) {
form.value.product_info.brand_id = brandOptions.value[0].id;
}
} catch (error) {
showErrorToast(error, "补录选项加载失败");
} finally {
@@ -101,27 +78,11 @@ async function fetchMeta() {
function onProviderChange(event: any) {
const index = Number(event.detail?.value || 0);
form.value.service_provider = providerOptions[index]?.value || "anxinyan";
ensureBrandSelection();
}
function onCategoryChange(event: any) {
const index = Number(event.detail?.value || 0);
form.value.product_info.category_id = meta.value.categories[index]?.id || 0;
ensureBrandSelection();
}
function onBrandChange(event: any) {
const index = Number(event.detail?.value || 0);
form.value.product_info.brand_id = brandOptions.value[index]?.id || 0;
}
function onUsageChange(event: any) {
const index = Number(event.detail?.value || 0);
form.value.extra_info.usage_status = usageOptions[index]?.value || "";
}
function onPurchasePriceInput(event: any) {
purchasePriceInput.value = String(event.detail?.value ?? "");
}
function applyRegionSelection(selection: string[]) {
@@ -144,11 +105,6 @@ function onRegionChange(event: any) {
applyRegionSelection(buildRegionPickerState(indexes).selection);
}
function ensureBrandSelection() {
const current = brandOptions.value.find((item) => item.id === form.value.product_info.brand_id);
form.value.product_info.brand_id = current?.id || brandOptions.value[0]?.id || 0;
}
function pickerText(options: Array<{ label?: string; name?: string }>, index: number, fallback: string) {
const item = options[index];
return item?.label || item?.name || fallback;
@@ -157,8 +113,8 @@ function pickerText(options: Array<{ label?: string; name?: string }>, index: nu
function validateForm() {
const product = form.value.product_info;
const address = form.value.return_address;
if (!product.category_id || !product.brand_id) {
showInfoToast("请完整填写品类和品牌");
if (!product.category_id) {
showInfoToast("请选择品类");
return false;
}
if (!address.consignee.trim() || !address.mobile.trim() || !address.province.trim() || !address.city.trim() || !address.district.trim() || !address.detail_address.trim()) {
@@ -168,53 +124,13 @@ function validateForm() {
return true;
}
async function chooseImageFiles() {
try {
const result = await uni.chooseImage({
count: 9,
sizeType: ["compressed"],
sourceType: ["album", "camera"],
});
if (!result.tempFilePaths?.length) return;
uploading.value = true;
for (const filePath of result.tempFilePaths) {
const asset = await adminApi.uploadManualOrderFile(filePath);
form.value.materials[0].files.push(asset);
}
showInfoToast("图片上传成功");
} catch (error) {
showErrorToast(error, "图片上传失败");
} finally {
uploading.value = false;
}
}
async function chooseVideoFile() {
try {
const result = await uni.chooseVideo({ sourceType: ["album", "camera"] });
if (!result.tempFilePath) return;
uploading.value = true;
const asset = await adminApi.uploadManualOrderFile(result.tempFilePath);
form.value.materials[0].files.push(asset);
showInfoToast("视频上传成功");
} catch (error) {
showErrorToast(error, "视频上传失败");
} finally {
uploading.value = false;
}
}
function removeFile(file: AdminFileAsset) {
form.value.materials[0].files = form.value.materials[0].files.filter((item) => item.file_url !== file.file_url);
}
async function submitManualOrder() {
if (!validateForm() || submitting.value || uploading.value) return;
submitting.value = true;
if (!validateForm() || submitting.value) return;
submitting.value = true;
try {
const payload = JSON.parse(JSON.stringify(form.value)) as AdminManualOrderCreatePayload;
const purchasePrice = Number(purchasePriceInput.value.trim());
payload.extra_info.purchase_price = Number.isFinite(purchasePrice) && purchasePrice > 0 ? purchasePrice : 0;
payload.product_info.brand_id = 0;
payload.product_info.brand_name = payload.product_info.brand_name.trim();
const response = await withLoading("正在创建", () => adminApi.createManualOrder(payload));
showInfoToast(`已创建 ${response.order_no}`);
uni.redirectTo({ url: `/pages/order/detail?id=${response.order_id}` });
@@ -248,41 +164,13 @@ onLoad(() => {
<picker :range="meta.categories" range-key="name" :value="categoryIndex" @change="onCategoryChange">
<view class="field picker-field">{{ pickerText(meta.categories, categoryIndex, "选择品类") }}</view>
</picker>
<picker :range="brandOptions" range-key="name" :value="brandIndex" @change="onBrandChange">
<view class="field picker-field">{{ pickerText(brandOptions, brandIndex, "选择品牌") }}</view>
</picker>
<input v-model="form.product_info.brand_name" class="field" maxlength="128" placeholder="品牌名称,可不填" />
<input v-model="form.product_info.product_name" class="field" placeholder="商品名称,可选" />
<input v-model="form.product_info.color" class="field" placeholder="颜色,可选" />
<input v-model="form.product_info.size_spec" class="field" placeholder="规格 / 尺寸,可选" />
<input v-model="form.product_info.serial_no" class="field" placeholder="序列号,可选" />
</view>
<view class="card stack">
<view class="card-title">补充信息</view>
<view class="field-group">
<view class="field-label">购买渠道</view>
<input v-model="form.extra_info.purchase_channel" class="field" placeholder="请输入购买渠道,可选" />
</view>
<view class="field-group">
<view class="field-label">购买价格</view>
<input :value="purchasePriceInput" class="field" type="digit" placeholder="请输入购买价格,可选" @input="onPurchasePriceInput" />
</view>
<view class="field-group">
<view class="field-label">使用情况</view>
<picker :range="usageOptions" range-key="label" :value="usageIndex" @change="onUsageChange">
<view class="field picker-field">{{ pickerText(usageOptions, usageIndex, "请选择使用情况,可选") }}</view>
</picker>
</view>
<view class="field-group">
<view class="field-label">成色说明</view>
<textarea v-model="form.extra_info.condition_desc" class="textarea" placeholder="请输入成色说明,可选" />
</view>
<view class="field-group">
<view class="field-label">内部备注</view>
<textarea v-model="form.extra_info.remark" class="textarea" placeholder="请输入内部备注,可选" />
</view>
</view>
<view class="card stack">
<view class="card-title">寄回信息</view>
<input v-model="form.return_address.consignee" class="field" placeholder="收件人" />
@@ -303,26 +191,7 @@ onLoad(() => {
<input v-model="form.return_address.detail_address" class="field" placeholder="详细地址" />
</view>
<view class="card stack">
<view class="row">
<view>
<view class="card-title">初始资料</view>
<view class="card-desc">{{ materialFiles.length }} 个文件</view>
</view>
</view>
<view class="upload-actions">
<button class="btn btn--ghost" :disabled="uploading" @click="chooseImageFiles">{{ uploading ? "上传中" : "添加图片" }}</button>
<button class="btn btn--ghost" :disabled="uploading" @click="chooseVideoFile">{{ uploading ? "上传中" : "添加视频" }}</button>
</view>
<view v-if="materialFiles.length" class="file-list">
<view v-for="file in materialFiles" :key="file.file_url" class="file-item">
<text class="file-name">{{ file.name || file.file_id }}</text>
<text class="tag tag--danger" @click="removeFile(file)">移除</text>
</view>
</view>
</view>
<button class="btn btn--primary submit-button" :disabled="submitting || uploading" @click="submitManualOrder">
<button class="btn btn--primary submit-button" :disabled="submitting" @click="submitManualOrder">
{{ submitting ? "创建中" : "创建补录订单" }}
</button>
</template>