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

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

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { ElMessage, ElMessageBox } from "element-plus"; import { ElMessage, ElMessageBox } from "element-plus";
import { adminApi, type AdminFileAsset, type AdminManualOrderCreatePayload, type AdminManualOrderMeta, type AdminOrderDetail, type AdminOrderListItem, type AdminOrderWarehouseOption } from "../../api/admin"; import { adminApi, type AdminManualOrderCreatePayload, type AdminManualOrderMeta, type AdminOrderDetail, type AdminOrderListItem, type AdminOrderWarehouseOption } from "../../api/admin";
import OrderStatusTag from "../../components/OrderStatusTag.vue"; import OrderStatusTag from "../../components/OrderStatusTag.vue";
const loading = ref(false); const loading = ref(false);
@@ -21,7 +21,6 @@ const returnTrackingNo = ref("");
const manualDialogVisible = ref(false); const manualDialogVisible = ref(false);
const manualSubmitting = ref(false); const manualSubmitting = ref(false);
const manualMetaLoading = ref(false); const manualMetaLoading = ref(false);
const manualUploading = ref(false);
const manualMeta = ref<AdminManualOrderMeta>({ categories: [], brands: [] }); const manualMeta = ref<AdminManualOrderMeta>({ categories: [], brands: [] });
const manualForm = ref<AdminManualOrderCreatePayload>(createManualOrderForm()); const manualForm = ref<AdminManualOrderCreatePayload>(createManualOrderForm());
@@ -114,22 +113,13 @@ const logisticsActionText = computed(() => {
const canSubmitReturnLogistics = computed(() => Boolean(detail.value?.order_info.can_submit_return_logistics)); const canSubmitReturnLogistics = computed(() => Boolean(detail.value?.order_info.can_submit_return_logistics));
const returnLogisticsBlockReason = computed(() => detail.value?.order_info.return_logistics_block_reason || ""); const returnLogisticsBlockReason = computed(() => detail.value?.order_info.return_logistics_block_reason || "");
const canMarkReturnReceived = computed(() => Boolean(detail.value?.order_info.can_mark_return_received)); 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 { function createManualOrderForm(): AdminManualOrderCreatePayload {
return { return {
service_provider: "anxinyan", service_provider: "anxinyan",
product_info: { product_info: {
category_id: 0, category_id: 0,
brand_id: 0, brand_id: 0,
brand_name: "",
product_name: "", product_name: "",
color: "", color: "",
size_spec: "", size_spec: "",
@@ -180,7 +170,7 @@ async function fetchOrders() {
} }
async function ensureManualMeta() { async function ensureManualMeta() {
if (manualMeta.value.categories.length && manualMeta.value.brands.length) return; if (manualMeta.value.categories.length) return;
manualMetaLoading.value = true; manualMetaLoading.value = true;
try { try {
const response = await adminApi.getManualOrderMeta(); const response = await adminApi.getManualOrderMeta();
@@ -199,17 +189,10 @@ async function openManualDialog() {
await ensureManualMeta(); 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() { function validateManualForm() {
const form = manualForm.value; const form = manualForm.value;
if (!form.product_info.category_id || !form.product_info.brand_id) { if (!form.product_info.category_id) {
ElMessage.warning("请完整填写品类和品牌"); ElMessage.warning("请选择品类");
return false; return false;
} }
const address = form.return_address; const address = form.return_address;
@@ -220,29 +203,13 @@ function validateManualForm() {
return true; 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() { async function submitManualOrder() {
if (!validateManualForm()) return; if (!validateManualForm()) return;
manualSubmitting.value = true; manualSubmitting.value = true;
try { try {
const payload: AdminManualOrderCreatePayload = JSON.parse(JSON.stringify(manualForm.value)); const payload: AdminManualOrderCreatePayload = JSON.parse(JSON.stringify(manualForm.value));
payload.product_info.brand_id = 0;
payload.product_info.brand_name = payload.product_info.brand_name.trim();
const response = await adminApi.createManualOrder(payload); const response = await adminApi.createManualOrder(payload);
ElMessage.success(`补录订单已创建:${response.data.order_no} / ${response.data.appraisal_no}`); ElMessage.success(`补录订单已创建:${response.data.order_no} / ${response.data.appraisal_no}`);
manualDialogVisible.value = false; manualDialogVisible.value = false;
@@ -817,14 +784,12 @@ onMounted(fetchOrders);
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="品类"> <el-form-item label="品类">
<el-select v-model="manualForm.product_info.category_id" filterable style="width: 100%" @change="handleManualCategoryChange"> <el-select v-model="manualForm.product_info.category_id" filterable style="width: 100%">
<el-option v-for="item in manualMeta.categories" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in manualMeta.categories" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="品牌"> <el-form-item label="品牌(选填)">
<el-select v-model="manualForm.product_info.brand_id" filterable style="width: 100%"> <el-input v-model="manualForm.product_info.brand_name" maxlength="128" placeholder="请输入品牌名称,可不填" />
<el-option v-for="item in manualBrandOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="商品名称"> <el-form-item label="商品名称">
<el-input v-model="manualForm.product_info.product_name" placeholder="可选例如Classic Flap 手袋" /> <el-input v-model="manualForm.product_info.product_name" placeholder="可选例如Classic Flap 手袋" />
@@ -838,26 +803,7 @@ onMounted(fetchOrders);
<el-form-item label="序列号"> <el-form-item label="序列号">
<el-input v-model="manualForm.product_info.serial_no" placeholder="可选" /> <el-input v-model="manualForm.product_info.serial_no" placeholder="可选" />
</el-form-item> </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> </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> </el-form>
</div> </div>
@@ -887,30 +833,10 @@ onMounted(fetchOrders);
</el-form> </el-form>
</div> </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> </div>
<template #footer> <template #footer>
<el-button @click="manualDialogVisible = false">取消</el-button> <el-button @click="manualDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="manualSubmitting" :disabled="manualUploading || manualMetaLoading" @click="submitManualOrder">创建补录订单</el-button> <el-button type="primary" :loading="manualSubmitting" :disabled="manualMetaLoading" @click="submitManualOrder">创建补录订单</el-button>
</template> </template>
</el-dialog> </el-dialog>
</template> </template>

View File

@@ -938,6 +938,7 @@ class OrdersController
$categoryId = (int)($productInput['category_id'] ?? 0); $categoryId = (int)($productInput['category_id'] ?? 0);
$brandId = (int)($productInput['brand_id'] ?? 0); $brandId = (int)($productInput['brand_id'] ?? 0);
$brandName = $this->limitManualText(trim((string)($productInput['brand_name'] ?? '')), 128);
$productName = trim((string)($productInput['product_name'] ?? '')); $productName = trim((string)($productInput['product_name'] ?? ''));
$consignee = trim((string)($returnAddressInput['consignee'] ?? '')); $consignee = trim((string)($returnAddressInput['consignee'] ?? ''));
$mobile = trim((string)($returnAddressInput['mobile'] ?? '')); $mobile = trim((string)($returnAddressInput['mobile'] ?? ''));
@@ -949,8 +950,8 @@ class OrdersController
if ($serviceProvider === '') { if ($serviceProvider === '') {
return api_error('服务类型不正确', 422); return api_error('服务类型不正确', 422);
} }
if ($categoryId <= 0 || $brandId <= 0) { if ($categoryId <= 0) {
return api_error('请完整填写品类和品牌', 422); return api_error('请选择品类', 422);
} }
if ($consignee === '' || $mobile === '' || $province === '' || $city === '' || $district === '' || $detailAddress === '') { if ($consignee === '' || $mobile === '' || $province === '' || $city === '' || $district === '' || $detailAddress === '') {
return api_error('请完整填写寄回收件信息', 422); return api_error('请完整填写寄回收件信息', 422);
@@ -960,9 +961,18 @@ class OrdersController
if (!$category) { if (!$category) {
return api_error('品类不存在', 422); return api_error('品类不存在', 422);
} }
$brand = Db::name('catalog_brands')->where('id', $brandId)->find(); $brand = null;
if (!$brand) { if ($brandId > 0) {
return api_error('品牌不存在', 422); $brand = Db::name('catalog_brands')->where('id', $brandId)->find();
if (!$brand) {
return api_error('品牌不存在', 422);
}
if ($brandName === '') {
$brandName = (string)$brand['name'];
}
}
if ($productName === '') {
$productName = trim((string)$category['name'] . ' ' . $brandName);
} }
$now = date('Y-m-d H:i:s'); $now = date('Y-m-d H:i:s');
@@ -1006,8 +1016,8 @@ class OrdersController
'order_id' => $orderId, 'order_id' => $orderId,
'category_id' => $categoryId, 'category_id' => $categoryId,
'category_name' => (string)$category['name'], 'category_name' => (string)$category['name'],
'brand_id' => $brandId, 'brand_id' => $brandId > 0 ? $brandId : null,
'brand_name' => (string)$brand['name'], 'brand_name' => $brandName,
'color' => trim((string)($productInput['color'] ?? '')), 'color' => trim((string)($productInput['color'] ?? '')),
'size_spec' => trim((string)($productInput['size_spec'] ?? '')), 'size_spec' => trim((string)($productInput['size_spec'] ?? '')),
'serial_no' => trim((string)($productInput['serial_no'] ?? '')), 'serial_no' => trim((string)($productInput['serial_no'] ?? '')),
@@ -1547,6 +1557,15 @@ class OrdersController
}; };
} }
private function limitManualText(string $value, int $maxLength): string
{
if (function_exists('mb_substr')) {
return mb_substr($value, 0, $maxLength, 'UTF-8');
}
return substr($value, 0, $maxLength);
}
private function formatAdminLogisticsDesc(string $logisticsType, string $status, string $expressCompany, string $trackingNo, string $fallback): string private function formatAdminLogisticsDesc(string $logisticsType, string $status, string $expressCompany, string $trackingNo, string $fallback): string
{ {
$expressCompany = trim($expressCompany); $expressCompany = trim($expressCompany);

View File

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

View File

@@ -1,43 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { onLoad } from "@dcloudio/uni-app"; 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 { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
import { buildRegionPickerState, updateRegionPickerIndexes } from "../../utils/regions"; import { buildRegionPickerState, updateRegionPickerIndexes } from "../../utils/regions";
const loading = ref(false); const loading = ref(false);
const submitting = ref(false); const submitting = ref(false);
const uploading = ref(false);
const meta = ref<AdminManualOrderMeta>({ categories: [], brands: [] }); const meta = ref<AdminManualOrderMeta>({ categories: [], brands: [] });
const form = ref<AdminManualOrderCreatePayload>(createForm()); const form = ref<AdminManualOrderCreatePayload>(createForm());
const purchasePriceInput = ref("");
const regionPickerIndexes = ref<[number, number, number]>([0, 0, 0]); const regionPickerIndexes = ref<[number, number, number]>([0, 0, 0]);
const providerOptions = [ const providerOptions = [
{ label: "实物鉴定", value: "anxinyan" }, { label: "实物鉴定", value: "anxinyan" },
{ label: "中检鉴定", value: "zhongjian" }, { 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 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 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 selectedRegionText = computed(() => {
const { province, city, district } = form.value.return_address; const { province, city, district } = form.value.return_address;
return province && city && district ? `${province} / ${city} / ${district}` : ""; return province && city && district ? `${province} / ${city} / ${district}` : "";
@@ -50,6 +29,7 @@ function createForm(): AdminManualOrderCreatePayload {
product_info: { product_info: {
category_id: 0, category_id: 0,
brand_id: 0, brand_id: 0,
brand_name: "",
product_name: "", product_name: "",
color: "", color: "",
size_spec: "", size_spec: "",
@@ -88,9 +68,6 @@ async function fetchMeta() {
if (!form.value.product_info.category_id && meta.value.categories.length) { if (!form.value.product_info.category_id && meta.value.categories.length) {
form.value.product_info.category_id = meta.value.categories[0].id; 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) { } catch (error) {
showErrorToast(error, "补录选项加载失败"); showErrorToast(error, "补录选项加载失败");
} finally { } finally {
@@ -101,27 +78,11 @@ async function fetchMeta() {
function onProviderChange(event: any) { function onProviderChange(event: any) {
const index = Number(event.detail?.value || 0); const index = Number(event.detail?.value || 0);
form.value.service_provider = providerOptions[index]?.value || "anxinyan"; form.value.service_provider = providerOptions[index]?.value || "anxinyan";
ensureBrandSelection();
} }
function onCategoryChange(event: any) { function onCategoryChange(event: any) {
const index = Number(event.detail?.value || 0); const index = Number(event.detail?.value || 0);
form.value.product_info.category_id = meta.value.categories[index]?.id || 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[]) { function applyRegionSelection(selection: string[]) {
@@ -144,11 +105,6 @@ function onRegionChange(event: any) {
applyRegionSelection(buildRegionPickerState(indexes).selection); 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) { function pickerText(options: Array<{ label?: string; name?: string }>, index: number, fallback: string) {
const item = options[index]; const item = options[index];
return item?.label || item?.name || fallback; return item?.label || item?.name || fallback;
@@ -157,8 +113,8 @@ function pickerText(options: Array<{ label?: string; name?: string }>, index: nu
function validateForm() { function validateForm() {
const product = form.value.product_info; const product = form.value.product_info;
const address = form.value.return_address; const address = form.value.return_address;
if (!product.category_id || !product.brand_id) { if (!product.category_id) {
showInfoToast("请完整填写品类和品牌"); showInfoToast("请选择品类");
return false; return false;
} }
if (!address.consignee.trim() || !address.mobile.trim() || !address.province.trim() || !address.city.trim() || !address.district.trim() || !address.detail_address.trim()) { 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; 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() { async function submitManualOrder() {
if (!validateForm() || submitting.value || uploading.value) return; if (!validateForm() || submitting.value) return;
submitting.value = true; submitting.value = true;
try { try {
const payload = JSON.parse(JSON.stringify(form.value)) as AdminManualOrderCreatePayload; const payload = JSON.parse(JSON.stringify(form.value)) as AdminManualOrderCreatePayload;
const purchasePrice = Number(purchasePriceInput.value.trim()); payload.product_info.brand_id = 0;
payload.extra_info.purchase_price = Number.isFinite(purchasePrice) && purchasePrice > 0 ? purchasePrice : 0; payload.product_info.brand_name = payload.product_info.brand_name.trim();
const response = await withLoading("正在创建", () => adminApi.createManualOrder(payload)); const response = await withLoading("正在创建", () => adminApi.createManualOrder(payload));
showInfoToast(`已创建 ${response.order_no}`); showInfoToast(`已创建 ${response.order_no}`);
uni.redirectTo({ url: `/pages/order/detail?id=${response.order_id}` }); 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"> <picker :range="meta.categories" range-key="name" :value="categoryIndex" @change="onCategoryChange">
<view class="field picker-field">{{ pickerText(meta.categories, categoryIndex, "选择品类") }}</view> <view class="field picker-field">{{ pickerText(meta.categories, categoryIndex, "选择品类") }}</view>
</picker> </picker>
<picker :range="brandOptions" range-key="name" :value="brandIndex" @change="onBrandChange"> <input v-model="form.product_info.brand_name" class="field" maxlength="128" placeholder="品牌名称,可不填" />
<view class="field picker-field">{{ pickerText(brandOptions, brandIndex, "选择品牌") }}</view>
</picker>
<input v-model="form.product_info.product_name" class="field" 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.color" class="field" placeholder="颜色,可选" />
<input v-model="form.product_info.size_spec" 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="序列号,可选" /> <input v-model="form.product_info.serial_no" class="field" placeholder="序列号,可选" />
</view> </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 stack">
<view class="card-title">寄回信息</view> <view class="card-title">寄回信息</view>
<input v-model="form.return_address.consignee" class="field" placeholder="收件人" /> <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="详细地址" /> <input v-model="form.return_address.detail_address" class="field" placeholder="详细地址" />
</view> </view>
<view class="card stack"> <button class="btn btn--primary submit-button" :disabled="submitting" @click="submitManualOrder">
<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">
{{ submitting ? "创建中" : "创建补录订单" }} {{ submitting ? "创建中" : "创建补录订单" }}
</button> </button>
</template> </template>