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