This commit is contained in:
2026-04-13 15:32:34 +08:00
parent 54d3110d1a
commit e661e13833
3 changed files with 244 additions and 174 deletions
+1 -1
View File
@@ -81,7 +81,7 @@
"order_info": "Order Information", "order_info": "Order Information",
"title": "Title", "title": "Title",
"input_title": "Enter order title", "input_title": "Enter order title",
"remarks": "Remarks", "remarks": "Usage Remarks",
"photo_remarks": "Photos Remarks", "photo_remarks": "Photos Remarks",
"remarks_text": "Enter text notes", "remarks_text": "Enter text notes",
"purchase_channel": "Purchase Channel", "purchase_channel": "Purchase Channel",
+1 -1
View File
@@ -81,7 +81,7 @@
"order_info": "订单信息", "order_info": "订单信息",
"title": "标题", "title": "标题",
"input_title": "输入订单标题", "input_title": "输入订单标题",
"remarks": "备注", "remarks": "用途备注",
"photo_remarks": "图片备注", "photo_remarks": "图片备注",
"remarks_text": "输入文字备注", "remarks_text": "输入文字备注",
"purchase_channel": "采购途径", "purchase_channel": "采购途径",
@@ -22,93 +22,98 @@
*/ */
// ==================== 依赖导入 ==================== // ==================== 依赖导入 ====================
import { reactive, ref, computed, watch } from 'vue' import { reactive, ref, computed, watch } from "vue";
import { useI18n } from 'vue-i18n' import { useI18n } from "vue-i18n";
import { useToastStore } from '@/stores/toast' import { useToastStore } from "@/stores/toast";
import { usePageTitle } from '@/composables/usePageTitle' import { usePageTitle } from "@/composables/usePageTitle";
import { useValidation } from '@/composables' import { useValidation } from "@/composables";
import { purchaseApi } from '@/api/purchase' import { purchaseApi } from "@/api/purchase";
// 组件导入 // 组件导入
import tagadder from '@/components/tagadder.vue' // 标签添加组件 import tagadder from "@/components/tagadder.vue"; // 标签添加组件
import datePicker from '@/components/datePicker.vue' // 日期选择组件 import datePicker from "@/components/datePicker.vue"; // 日期选择组件
import useDropzone from '@/components/useDropzone.vue' // 文件上传组件(图片) import useDropzone from "@/components/useDropzone.vue"; // 文件上传组件(图片)
// ==================== 页面初始化 ==================== // ==================== 页面初始化 ====================
// 设置页面标题,使用 i18n key // 设置页面标题,使用 i18n key
usePageTitle('purchase.add_part') usePageTitle("purchase.add_part");
// 获取国际化实例,用于多语言文本 // 获取国际化实例,用于多语言文本
const { t, locale } = useI18n() const { t, locale } = useI18n();
// Toast 消息提示 // Toast 消息提示
const toast = useToastStore() const toast = useToastStore();
// 表单验证工具 // 表单验证工具
const { validate, errors, clearErrors } = useValidation() const { validate, errors, clearErrors } = useValidation();
// ==================== 常量定义 ==================== // ==================== 常量定义 ====================
/** /**
* 备注文字最大长度限制 * 备注文字最大长度限制
* 超过此长度将显示字符计数警告 * 超过此长度将显示字符计数警告
*/ */
const textMaxLen = 256 const textMaxLen = 256;
/** /**
* 文件上传组件的引用 * 文件上传组件的引用
* 用于获取已上传的图片列表 * 用于获取已上传的图片列表
*/ */
const photosRef = ref(null) const photosRef = ref(null);
/** /**
* 货币类型选项 * 货币类型选项
* key: 数据库中存储的值 * key: 数据库中存储的值
* value: 显示的货币符号 * value: 显示的货币符号
*/ */
const currencyOptions = { 1: 'CNY', 2: 'MOP', 3: 'HKD', 4: 'USD' } const currencyOptions = { 1: "CNY", 2: "MOP", 3: "HKD", 4: "USD" };
/** /**
* 货币类型对应的国旗图标路径 * 货币类型对应的国旗图标路径
*/ */
const currencyFlags = { 1: '/static/flags/cn.svg', 2: '/static/flags/mo.svg', 3: '/static/flags/hk.svg', 4: '/static/flags/us.svg' } const currencyFlags = {
1: "/static/flags/cn.svg",
2: "/static/flags/mo.svg",
3: "/static/flags/hk.svg",
4: "/static/flags/us.svg",
};
// ==================== 费用类型映射 ==================== // ==================== 费用类型映射 ====================
// 费用类型:单价 / 运费 // 费用类型:单价 / 运费
const costType = computed(() => ({ const costType = computed(() => ({
1: t('cost_type.unit_price'), // 单价 1: t("cost_type.unit_price"), // 单价
2: t('cost_type.freight'), // 运费 2: t("cost_type.freight"), // 运费
})) }));
// ==================== 订单状态映射 ==================== // ==================== 订单状态映射 ====================
// 订单的各种状态选项 // 订单的各种状态选项
const orderStatus = computed(() => ({ const orderStatus = computed(() => ({
1: t('order_status.pending_order'), // 待下单 1: t("order_status.pending_order"), // 待下单
2: t('order_status.order_placed'), // 已下单 2: t("order_status.order_placed"), // 已下单
3: t('order_status.in_transit'), // 运输中 3: t("order_status.in_transit"), // 运输中
4: t('order_status.completed'), // 已完成 4: t("order_status.completed"), // 已完成
5: t('order_status.refund_requested'), // 退款中 5: t("order_status.refund_requested"), // 退款中
6: t('order_status.returning'), // 退货中 6: t("order_status.returning"), // 退货中
7: t('order_status.refunded'), // 已退款 7: t("order_status.refunded"), // 已退款
8: t('order_status.lost_package'), // 丢件 8: t("order_status.lost_package"), // 丢件
})) }));
// ==================== 费用明细管理 ==================== // ==================== 费用明细管理 ====================
/** /**
* 已添加的费用明细列表 * 已添加的费用明细列表
* 每项包含:类型、数量、单价、总价、货币类型 * 每项包含:类型、数量、单价、总价、货币类型
*/ */
const costEntries = reactive([]) const costEntries = reactive([]);
/** /**
* 新费用条目的临时数据 * 新费用条目的临时数据
* 用户填写完表单后点击"添加"按钮加入 costEntries * 用户填写完表单后点击"添加"按钮加入 costEntries
*/ */
const newCost = reactive({ const newCost = reactive({
type: '1', // 费用类型:默认"单价" type: "1", // 费用类型:默认"单价"
int: 1, // 数量:默认1 int: 1, // 数量:默认1
cost: 0, // 单价:默认0 cost: 0, // 单价:默认0
currencyType: '1', // 货币类型:默认人民币 currencyType: "1", // 货币类型:默认人民币
}) });
// ==================== 表单数据 ==================== // ==================== 表单数据 ====================
/** /**
@@ -116,44 +121,44 @@ const newCost = reactive({
* 包含所有需要提交的字段 * 包含所有需要提交的字段
*/ */
const form = reactive({ const form = reactive({
title: '', // 订单标题(必填) title: "", // 订单标题(必填)
remark: '', // 备注说明 remark: "", // 备注说明
photos: [], // 图片列表(上传后由 dropzone 填充) photos: [], // 图片列表(上传后由 dropzone 填充)
link: '', // 采购链接 link: "", // 采购链接
partname: '', // 配件名称 partname: "", // 配件名称
styles: '', // 款式标签 styles: "", // 款式标签
costs: [], // 费用明细(提交前由 costEntries 转换) costs: [], // 费用明细(提交前由 costEntries 转换)
tracking_number: '', // 快递单号 tracking_number: "", // 快递单号
updatetime: '', // 更新时间 updatetime: "", // 更新时间
order_status: '1', // 订单状态(默认待下单) order_status: "1", // 订单状态(默认待下单)
}) });
/** /**
* 计算新费用条目的总价 * 计算新费用条目的总价
* 总价 = 数量 × 单价,保留2位小数 * 总价 = 数量 × 单价,保留2位小数
*/ */
const newCostTotal = computed(() => const newCostTotal = computed(() =>
parseFloat((newCost.int * newCost.cost).toFixed(2)) parseFloat((newCost.int * newCost.cost).toFixed(2)),
) );
/** /**
* 添加一条费用明细到列表 * 添加一条费用明细到列表
* 条件:单价必须大于0 * 条件:单价必须大于0
*/ */
function addCostEntry() { function addCostEntry() {
if (newCost.cost <= 0) return if (newCost.cost <= 0) return;
costEntries.push({ costEntries.push({
type: newCost.type, type: newCost.type,
int: newCost.int, int: newCost.int,
cost: newCost.cost, cost: newCost.cost,
costt: newCostTotal.value, costt: newCostTotal.value,
currencytype: newCost.currencyType, currencytype: newCost.currencyType,
}) });
// 添加后重置表单,以便继续添加下一条 // 添加后重置表单,以便继续添加下一条
newCost.type = '1' newCost.type = "1";
newCost.int = 1 newCost.int = 1;
newCost.cost = 0 newCost.cost = 0;
newCost.currencyType = '1' newCost.currencyType = "1";
} }
/** /**
@@ -161,25 +166,26 @@ function addCostEntry() {
* @param {number} index - 要删除的条目索引 * @param {number} index - 要删除的条目索引
*/ */
function removeCostEntry(index) { function removeCostEntry(index) {
costEntries.splice(index, 1) costEntries.splice(index, 1);
} }
/** /**
* 监听单价输入,自动保留2位小数 * 监听单价输入,自动保留2位小数
* 防止用户输入如 10.999 这样的值 * 防止用户输入如 10.999 这样的值
*/ */
watch(() => newCost.cost, (val) => { watch(
const fixed = parseFloat(val).toFixed(2) () => newCost.cost,
if (parseFloat(fixed) !== val) newCost.cost = parseFloat(fixed) (val) => {
}) const fixed = parseFloat(val).toFixed(2);
if (parseFloat(fixed) !== val) newCost.cost = parseFloat(fixed);
},
);
/** /**
* 提交按钮 loading 状态 * 提交按钮 loading 状态
* 防止重复提交 * 防止重复提交
*/ */
const loading = ref(false) const loading = ref(false);
// ==================== 表单提交 ==================== // ==================== 表单提交 ====================
/** /**
@@ -191,44 +197,44 @@ const loading = ref(false)
*/ */
async function handleSubmit() { async function handleSubmit() {
// 清空之前的验证错误 // 清空之前的验证错误
clearErrors() clearErrors();
// 验证标题是否填写 // 验证标题是否填写
const err = validate('title', form.title, t('purchase_addorder.title')) const err = validate("title", form.title, t("purchase_addorder.title"));
if (!err) return if (!err) return;
// 从 dropzone 组件获取已上传的图片文件名 // 从 dropzone 组件获取已上传的图片文件名
form.photos = [] form.photos = [];
if (photosRef.value?.has_some_files) { if (photosRef.value?.has_some_files) {
const result = photosRef.value.get_some_files() const result = photosRef.value.get_some_files();
form.photos = result.map(f => f.name) form.photos = result.map((f) => f.name);
} }
// 将费用明细转换为提交格式 // 将费用明细转换为提交格式
// 注意:金额需要从"元"转为"分"(乘以100)存储 // 注意:金额需要从"元"转为"分"(乘以100)存储
form.costs = costEntries.map(h => ({ form.costs = costEntries.map((h) => ({
...h, ...h,
cost: Math.round(h.cost * 100), cost: Math.round(h.cost * 100),
costt: Math.round(h.costt * 100), costt: Math.round(h.costt * 100),
})) }));
// 开始 loading // 开始 loading
loading.value = true loading.value = true;
try { try {
// 调用采购 API 添加订单 // 调用采购 API 添加订单
const { errCode } = await purchaseApi.addOrder(form) const { errCode } = await purchaseApi.addOrder(form);
if (errCode === 0) { if (errCode === 0) {
// 保存成功,显示成功提示 // 保存成功,显示成功提示
toast.success(t('message.save_ok')) toast.success(t("message.save_ok"));
} else { } else {
// 服务器错误,显示错误提示 // 服务器错误,显示错误提示
toast.error(t('message.server_error')) toast.error(t("message.server_error"));
} }
} catch { } catch {
// 错误已被 HTTP 拦截器处理,此处无需额外处理 // 错误已被 HTTP 拦截器处理,此处无需额外处理
} finally { } finally {
// 无论成功失败,都要关闭 loading // 无论成功失败,都要关闭 loading
loading.value = false loading.value = false;
} }
} }
</script> </script>
@@ -237,37 +243,50 @@ async function handleSubmit() {
<!-- 页面容器居中最大宽度 6xl左右内边距上下内边距 --> <!-- 页面容器居中最大宽度 6xl左右内边距上下内边距 -->
<div class="mx-auto max-w-6xl px-6 py-6"> <div class="mx-auto max-w-6xl px-6 py-6">
<!-- 主卡片白色背景圆角阴影支持暗色模式 --> <!-- 主卡片白色背景圆角阴影支持暗色模式 -->
<div class="flex flex-col gap-6 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"> <div
class="flex flex-col gap-6 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
>
<!-- ==================== 订单信息区块 ==================== --> <!-- ==================== 订单信息区块 ==================== -->
<div class="border-b border-gray-200 px-6 py-4 dark:border-dk-muted"> <div class="border-b border-gray-200 px-6 py-4 dark:border-dk-muted">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('purchase_addorder.order_info') }}</h4> <h4 class="text-sm font-semibold text-gray-900 dark:text-white">
{{ t("purchase_addorder.order_info") }}
</h4>
</div> </div>
<!-- 订单信息表单区域 --> <!-- 订单信息表单区域 -->
<div class="space-y-4 px-6 py-5"> <div class="space-y-4 px-6 py-5">
<!-- 标题字段必填 --> <!-- 标题字段必填 -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"> <label
{{ t('purchase_addorder.part_name') }} <span class="text-red-500">*</span> class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("purchase_addorder.part_name") }}
<span class="text-red-500">*</span>
</label> </label>
<input <input
v-model="form.title" v-model="form.title"
type="text" type="text"
maxlength="50"
class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white" class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white"
:class="errors.title ? 'border-red-500' : 'border-gray-300'" :class="errors.title ? 'border-red-500' : 'border-gray-300'"
:placeholder="t('purchase_addorder.part_name')" :placeholder="t('purchase_addorder.part_name')"
/> />
<!-- 验证错误提示 --> <!-- 验证错误提示 -->
<span v-if="errors.title" class="mt-1 block text-xs text-red-500">{{ errors.title }}</span> <span v-if="errors.title" class="mt-1 block text-xs text-red-500">{{
errors.title
}}</span>
</div> </div>
<!-- 备注字段 --> <!-- 备注字段 -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"> <label
{{ t('purchase_addorder.remarks') }} class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("purchase_addorder.remarks") }}
<!-- 字符计数 --> <!-- 字符计数 -->
<span class="text-gray-400">{{ form.remark.length }}/{{ textMaxLen }}</span> <span class="text-gray-400"
>{{ form.remark.length }}/{{ textMaxLen }}</span
>
</label> </label>
<textarea <textarea
v-model="form.remark" v-model="form.remark"
@@ -277,29 +296,12 @@ async function handleSubmit() {
/> />
</div> </div>
<!-- 图片上传区域 -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.photo_remarks') }}</label>
<!--
useDropzone 组件属性
- acceptFiles="image/*": 只接受图片文件
- uploadURL: 上传接口地址
- maxFiles="10": 最多10张图片
- ref="photosRef": 组件引用用于获取上传的文件列表
-->
<useDropzone acceptFiles="image/*" uploadURL="/api/files/upload/image" maxFiles="10" ref="photosRef" />
</div>
</div>
<!-- ==================== 采购渠道区块 ==================== -->
<div class="border-t border-gray-200 px-6 py-4 dark:border-dk-muted">
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('purchase_addorder.purchase_channel') }}</h4>
</div>
<div class="space-y-4 px-6 py-5">
<!-- 采购链接 --> <!-- 采购链接 -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.link') }}</label> <label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.link") }}</label
>
<textarea <textarea
v-model="form.link" v-model="form.link"
class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white" class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white"
@@ -307,44 +309,83 @@ async function handleSubmit() {
placeholder="url" placeholder="url"
/> />
</div> </div>
<!-- 款式标签 --> <!-- 款式标签 -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.style_remarks') }}</label> <label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.style_remarks") }}</label
>
<!-- tagadder: 支持添加多个标签的组件 --> <!-- tagadder: 支持添加多个标签的组件 -->
<tagadder :placeholder="t('purchase_addorder.add_style')" v-model="form.styles" /> <tagadder
:placeholder="t('purchase_addorder.add_style')"
v-model="form.styles"
/>
</div> </div>
<!-- ==================== 费用明细表格 ==================== --> <!-- ==================== 费用明细表格 ==================== -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.cost') }}</label> <label
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.cost") }}</label
>
<!-- 已添加的费用列表表格有时才显示 --> <!-- 已添加的费用列表表格有时才显示 -->
<div v-if="costEntries.length" class="mb-4 overflow-x-auto"> <div v-if="costEntries.length" class="mb-4 overflow-x-auto">
<table class="w-full text-left text-sm text-gray-900"> <table class="w-full text-left text-sm text-gray-900">
<thead> <thead>
<tr class="border-b border-gray-200 bg-gray-50 text-gray-500 dark:border-dk-muted dark:bg-dk-base"> <tr
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.type') }}</th> class="border-b border-gray-200 bg-gray-50 text-gray-500 dark:border-dk-muted dark:bg-dk-base"
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.quantity') }}</th> >
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.fee') }}</th> <th class="px-3 py-2 font-medium">
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.total_price') }}</th> {{ t("purchase_addorder.type") }}
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.currency') }}</th> </th>
<th class="px-3 py-2 font-medium">{{ t('purchase_addorder.operation') }}</th> <th class="px-3 py-2 font-medium">
{{ t("purchase_addorder.quantity") }}
</th>
<th class="px-3 py-2 font-medium">
{{ t("purchase_addorder.fee") }}
</th>
<th class="px-3 py-2 font-medium">
{{ t("purchase_addorder.total_price") }}
</th>
<th class="px-3 py-2 font-medium">
{{ t("purchase_addorder.currency") }}
</th>
<th class="px-3 py-2 font-medium">
{{ t("purchase_addorder.operation") }}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="(item, idx) in costEntries" :key="idx" class="border-b border-gray-100 dark:border-dk-muted"> <tr
<td class="px-3 py-2 font-medium text-gray-900 dark:text-white">{{ costType[item.type] }}</td> v-for="(item, idx) in costEntries"
:key="idx"
class="border-b border-gray-100 dark:border-dk-muted"
>
<td
class="px-3 py-2 font-medium text-gray-900 dark:text-white"
>
{{ costType[item.type] }}
</td>
<td class="px-3 py-2 text-gray-500">{{ item.int }}</td> <td class="px-3 py-2 text-gray-500">{{ item.int }}</td>
<td class="px-3 py-2 text-gray-500">{{ item.cost }}</td> <td class="px-3 py-2 text-gray-500">{{ item.cost }}</td>
<td class="px-3 py-2 text-gray-500">{{ item.costt }}</td> <td class="px-3 py-2 text-gray-500">{{ item.costt }}</td>
<td class="px-3 py-2 text-gray-500"> <td class="px-3 py-2 text-gray-500">
<img :src="currencyFlags[item.currencytype]" class="inline-block h-4 w-6 align-middle" :alt="currencyOptions[item.currencytype]" /> <img
:src="currencyFlags[item.currencytype]"
class="inline-block h-4 w-6 align-middle"
:alt="currencyOptions[item.currencytype]"
/>
{{ currencyOptions[item.currencytype] }} {{ currencyOptions[item.currencytype] }}
</td> </td>
<td class="px-3 py-2"> <td class="px-3 py-2">
<!-- 删除按钮 --> <!-- 删除按钮 -->
<button class="rounded px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20" @click="removeCostEntry(idx)">{{ t('purchase_addorder.remove') }}</button> <button
class="rounded px-2 py-1 text-xs font-medium text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
@click="removeCostEntry(idx)"
>
{{ t("purchase_addorder.remove") }}
</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -355,8 +396,13 @@ async function handleSubmit() {
<div class="grid grid-cols-2 gap-4 sm:grid-cols-5"> <div class="grid grid-cols-2 gap-4 sm:grid-cols-5">
<!-- 费用类型选择 --> <!-- 费用类型选择 -->
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-500">{{ t('purchase_addorder.fee_type') }}</label> <label class="mb-1 block text-xs font-medium text-gray-500">{{
<select v-model="newCost.type" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"> t("purchase_addorder.fee_type")
}}</label>
<select
v-model="newCost.type"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
>
<template v-for="(label, key) in costType" :key="key"> <template v-for="(label, key) in costType" :key="key">
<option :value="key">{{ label }}</option> <option :value="key">{{ label }}</option>
</template> </template>
@@ -365,20 +411,40 @@ async function handleSubmit() {
<!-- 数量输入 --> <!-- 数量输入 -->
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-500">{{ t('purchase_addorder.input_quantity') }}</label> <label class="mb-1 block text-xs font-medium text-gray-500">{{
<input v-model.number="newCost.int" type="number" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white" min="1" /> t("purchase_addorder.input_quantity")
}}</label>
<input
v-model.number="newCost.int"
type="number"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
min="1"
/>
</div> </div>
<!-- 单价输入 --> <!-- 单价输入 -->
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-500">{{ t('purchase_addorder.input_fee') }}</label> <label class="mb-1 block text-xs font-medium text-gray-500">{{
<input v-model="newCost.cost" type="number" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white" step="0.01" min="0" /> t("purchase_addorder.input_fee")
}}</label>
<input
v-model="newCost.cost"
type="number"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
step="0.01"
min="0"
/>
</div> </div>
<!-- 货币类型选择 --> <!-- 货币类型选择 -->
<div> <div>
<label class="mb-1 block text-xs font-medium text-gray-500">{{ t('purchase_addorder.select_currency') }}</label> <label class="mb-1 block text-xs font-medium text-gray-500">{{
<select v-model="newCost.currencyType" class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"> t("purchase_addorder.select_currency")
}}</label>
<select
v-model="newCost.currencyType"
class="w-full rounded-lg border border-gray-300 bg-white px-3 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white"
>
<template v-for="(label, key) in currencyOptions" :key="key"> <template v-for="(label, key) in currencyOptions" :key="key">
<option :value="key">{{ label }}</option> <option :value="key">{{ label }}</option>
</template> </template>
@@ -387,53 +453,42 @@ async function handleSubmit() {
<!-- 添加按钮与输入框底部对齐 --> <!-- 添加按钮与输入框底部对齐 -->
<div class="flex items-end"> <div class="flex items-end">
<button class="w-full rounded-lg border border-gray-300 bg-blue-600 px-3 py-2 text-sm font-semibold text-blue-100 transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:text-white dark:bg-blue-600" @click="addCostEntry">{{ t('purchase_addorder.add') }}</button> <button
class="w-full rounded-lg border border-gray-300 bg-blue-600 px-3 py-2 text-sm font-semibold text-blue-100 transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:text-white dark:bg-blue-600"
@click="addCostEntry"
>
{{ t("purchase_addorder.add") }}
</button>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- ==================== 订单状态区块 ==================== --> <!-- 图片上传区域 -->
<div class="border-t border-gray-200 px-6 py-4 dark:border-dk-muted"> <div>
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">{{ t('purchase_addorder.order_status') }}</h4> <label
</div> class="block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.photo_remarks") }}</label
<div class="px-6 py-5"> >
<!-- 三列响应式网格 --> <!--
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> useDropzone 组件属性
<!-- 更新时间必填 --> - acceptFiles="image/*": 只接受图片文件
<div> - uploadURL: 上传接口地址
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"> - maxFiles="10": 最多10张图片
{{ t('purchase_addorder.update_time') }} <span class="text-red-500">*</span> - ref="photosRef": 组件引用用于获取上传的文件列表
</label> -->
<datePicker v-model="form.updatetime" /> <useDropzone
</div> acceptFiles="image/*"
uploadURL="/api/files/upload/image"
<!-- 快递单号 --> maxFiles="10"
<div> ref="photosRef"
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.tracking_number') }}</label> />
<input
v-model="form.tracking_number"
type="text"
class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 dark:border-dk-muted dark:bg-dk-base dark:text-white"
:placeholder="t('purchase_addorder.input_tracking_number')"
/>
</div>
<!-- 订单状态下拉框 -->
<div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('purchase_addorder.order_status') }}</label>
<select v-model="form.order_status" class="w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2 text-sm dark:border-dk-muted dark:bg-dk-base dark:text-white">
<template v-for="(label, key) in orderStatus" :key="key">
<option :value="key">{{ label }}</option>
</template>
</select>
</div>
</div> </div>
</div> </div>
<!-- ==================== 底部操作栏 ==================== --> <!-- ==================== 底部操作栏 ==================== -->
<div class="flex justify-end border-t border-gray-200 px-6 py-4 dark:border-dk-muted"> <div
class="flex justify-end border-t border-gray-200 px-6 py-4 dark:border-dk-muted"
>
<!-- 提交按钮提交时显示 loading 动画 --> <!-- 提交按钮提交时显示 loading 动画 -->
<button <button
class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:active:scale-100" class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus:ring-2 focus:ring-blue-500/20 focus:outline-none disabled:active:scale-100"
@@ -441,14 +496,29 @@ async function handleSubmit() {
@click="handleSubmit" @click="handleSubmit"
> >
<!-- loading 状态显示旋转图标 --> <!-- loading 状态显示旋转图标 -->
<svg v-if="loading" class="h-4 w-4 animate-spin text-white" viewBox="0 0 24 24" fill="none"> <svg
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> v-if="loading"
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> class="h-4 w-4 animate-spin text-white"
viewBox="0 0 24 24"
fill="none"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
/>
</svg> </svg>
{{ t('purchase_addorder.submit') }} {{ t("purchase_addorder.submit") }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>