Simplify manual order creation
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user