This commit is contained in:
2026-04-14 14:30:30 +08:00
parent 2334588b26
commit aa843ebdb2
5 changed files with 196 additions and 68 deletions
+4 -2
View File
@@ -14,8 +14,9 @@ import (
type TabFileInfo_ struct { type TabFileInfo_ struct {
ID uint `gorm:"primaryKey;autoIncrement"` ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"not null;size:256;index"` // 前端报告的文件名 Name string `gorm:"not null;size:256;index"` // 前端报告的文件名
Path string `gorm:"not null;size:300"` // Size int64 `gorm:"not null"`
Sha256 string `gorm:"not null;size:64;index"` // Path string `gorm:"not null;size:300"` //
Sha256 string `gorm:"not null;size:64;index"` //
Mime string `gorm:"size:64;index"` Mime string `gorm:"size:64;index"`
Type string `gorm:"size:64;index"` Type string `gorm:"size:64;index"`
Const uint `gorm:"default:1;index"` Const uint `gorm:"default:1;index"`
@@ -128,6 +129,7 @@ func ApiFiles(r *gin.RouterGroup) {
//先检查数据库有没有数据 //先检查数据库有没有数据
fund_file_info := TabFileInfo_{ fund_file_info := TabFileInfo_{
Name: filename, Name: filename,
Size: file.Size,
Sha256: hash_str, Sha256: hash_str,
Mime: mimeType, Mime: mimeType,
Type: "image", Type: "image",
@@ -209,33 +209,36 @@ function return_files() {
// 加载初始已有文件(编辑场景) // 加载初始已有文件(编辑场景)
function loadInitialFiles() { function loadInitialFiles() {
if (!dropzoneInstance || !prop.initialFiles?.length) return; if (!dropzoneInstance || !prop.initialFiles?.length) return;
prop.initialFiles.forEach((f) => { prop.initialFiles.forEach((f) => {
// 构造 Dropzone 期望的 mock file 对象 // 构造 Dropzone 期望的 mock file 对象
const mockFile = { console.log(f)
name: f.Name || f.name || f.hash, // const mockFile = {
size: f.Size || f.size || 0, // name: f.Name,
type: f.Mime || f.mime || "image/jpeg", // size: f.Size,
status: Dropzone.SUCCESS, // type: f.Mime,
accepted: true, // status: Dropzone.SUCCESS,
upload: { uuid: f.Hash || f.hash || f.Sha256 }, // accepted: true,
previewElement: null, // upload: { uuid: f.Sha256 },
_removeLink: null, // previewElement: null,
}; // _removeLink: null,
// 通知 Dropzone "这是一个已存在的文件,不要上传" // };
dropzoneInstance.emit("addedfile", mockFile); // // 通知 Dropzone "这是一个已存在的文件,不要上传"
dropzoneInstance.emit("complete", mockFile); // dropzoneInstance.emit("addedfile", mockFile);
dropzoneInstance.files.push(mockFile); // dropzoneInstance.emit("complete", mockFile);
// 填充上传结果字段 // dropzoneInstance.files.push(mockFile);
const url = `/api/files/get/${f.Hash || f.hash || f.Sha256}`; // // 填充上传结果字段
files.push({ // const url = `/api/files/get/${f.Sha256}`;
uuid: f.Hash || f.hash || f.Sha256, // files.push({
hash: f.Hash || f.hash || f.Sha256, // uuid: f.Sha256,
get_url: url, // hash: f.Sha256,
download_url: `/api/files/download/${f.Hash || f.hash || f.Sha256}`, // get_url: url,
file_name: f.Name || f.name || "", // download_url: `/api/files/download/${f.Sha256}`,
file_size: f.Size || f.size || 0, // file_name: f.Name,
is_upload: true, // file_size: f.Size,
}); // is_upload: true,
// });
}); });
} }
+2 -1
View File
@@ -100,7 +100,8 @@
"commit_placeholder": "Add comment (optional)", "commit_placeholder": "Add comment (optional)",
"upload_photos": "Upload Photos", "upload_photos": "Upload Photos",
"commit_create": "Order created", "commit_create": "Order created",
"edit_order": "Edit Order" "edit_order": "Edit Order",
"submit_changes":"Submit changes"
}, },
"purchase_addorder": { "purchase_addorder": {
"add_order": "Add Order", "add_order": "Add Order",
+2 -1
View File
@@ -100,7 +100,8 @@
"commit_placeholder": "添加备注(可选)", "commit_placeholder": "添加备注(可选)",
"upload_photos": "上传图片", "upload_photos": "上传图片",
"commit_create": "订单创建", "commit_create": "订单创建",
"edit_order": "编辑订单" "edit_order": "编辑订单",
"submit_changes":"提交修改"
}, },
"purchase_addorder": { "purchase_addorder": {
"add_order": "添加订单", "add_order": "添加订单",
@@ -141,7 +141,9 @@ onMounted(async () => {
// 回填图片 // 回填图片
await nextTick(); await nextTick();
if (photos && photos.length > 0) { if (photos && photos.length > 0) {
dropzoneRef.value?.loadInitialFiles(photos); //dropzoneRef.value?.loadInitialFiles(photos);
//console.log(photos)
form.photos=photos
} }
} catch { } catch {
pageError.value = t("purchase.order_not_found"); pageError.value = t("purchase.order_not_found");
@@ -196,16 +198,33 @@ async function handleSubmit() {
<template> <template>
<div class="mx-auto max-w-6xl px-6 py-6"> <div class="mx-auto max-w-6xl px-6 py-6">
<!-- 加载中 --> <!-- 加载中 -->
<div v-if="pageLoading" class="flex items-center justify-center py-20 text-gray-400"> <div
v-if="pageLoading"
class="flex items-center justify-center py-20 text-gray-400"
>
<svg class="mr-2 h-5 w-5 animate-spin" viewBox="0 0 24 24" fill="none"> <svg class="mr-2 h-5 w-5 animate-spin" viewBox="0 0 24 24" fill="none">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> <circle
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" /> 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("message.loading") }} {{ t("message.loading") }}
</div> </div>
<!-- 订单不存在 --> <!-- 订单不存在 -->
<div v-else-if="pageError" class="rounded-xl border border-red-200 bg-red-50 px-6 py-10 text-center text-red-500 dark:border-red-800 dark:bg-red-900/20"> <div
v-else-if="pageError"
class="rounded-xl border border-red-200 bg-red-50 px-6 py-10 text-center text-red-500 dark:border-red-800 dark:bg-red-900/20"
>
{{ pageError }} {{ pageError }}
</div> </div>
@@ -215,7 +234,9 @@ async function handleSubmit() {
class="flex flex-col gap-0 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card" class="flex flex-col gap-0 rounded-xl border border-gray-200 bg-white shadow-lg dark:border-dk-muted dark:bg-dk-card"
> >
<!-- 顶部标题栏 --> <!-- 顶部标题栏 -->
<div class="flex items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-dk-muted"> <div
class="flex items-center justify-between 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"> <h4 class="text-sm font-semibold text-gray-900 dark:text-white">
{{ t("purchase_addorder.edit_order") }} {{ t("purchase_addorder.edit_order") }}
</h4> </h4>
@@ -224,15 +245,28 @@ async function handleSubmit() {
class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dk-base" class="flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-dk-base"
@click="router.back()" @click="router.back()"
> >
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"> <svg
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7" /> class="h-4 w-4"
fill="none"
stroke="currentColor"
stroke-width="2"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 19l-7-7 7-7"
/>
</svg> </svg>
{{ t("purchase.back_to_list") }} {{ t("purchase.back_to_list") }}
</button> </button>
</div> </div>
<!-- 错误提示字段验证 --> <!-- 错误提示字段验证 -->
<div v-if="errors.title" class="mx-6 mt-4 rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400"> <div
v-if="errors.title"
class="mx-6 mt-4 rounded-lg bg-red-50 px-4 py-2 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400"
>
{{ errors.title }} {{ errors.title }}
</div> </div>
@@ -246,7 +280,9 @@ async function handleSubmit() {
<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
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("purchase_addorder.part_name") }} {{ t("purchase_addorder.part_name") }}
<span class="text-red-500">*</span> <span class="text-red-500">*</span>
</label> </label>
@@ -258,14 +294,20 @@ async function handleSubmit() {
: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
class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>
{{ t("purchase_addorder.remarks") }} {{ 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,7 +319,10 @@ async function handleSubmit() {
<!-- 采购链接 --> <!-- 采购链接 -->
<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"
@@ -287,25 +332,48 @@ async function handleSubmit() {
<!-- 款式标签 --> <!-- 款式标签 -->
<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
<tagadder :placeholder="t('purchase_addorder.add_style')" v-model="form.styles" /> class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.style_remarks") }}</label
>
<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>
@@ -314,7 +382,9 @@ async function handleSubmit() {
:key="idx" :key="idx"
class="border-b border-gray-100 dark:border-dk-muted" class="border-b border-gray-100 dark:border-dk-muted"
> >
<td class="px-3 py-2 font-medium text-gray-900 dark:text-white"> <td
class="px-3 py-2 font-medium text-gray-900 dark:text-white"
>
{{ costType[item.type] }} {{ costType[item.type] }}
</td> </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>
@@ -339,16 +409,26 @@ 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">{{
t("purchase_addorder.fee_type")
}}</label>
<select <select
v-model="newCost.type" 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" 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"
> >
<option v-for="(label, key) in costType" :key="key" :value="Number(key)">{{ label }}</option> <option
v-for="(label, key) in costType"
:key="key"
:value="Number(key)"
>
{{ label }}
</option>
</select> </select>
</div> </div>
<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">{{
t("purchase_addorder.input_quantity")
}}</label>
<input <input
v-model.number="newCost.int" v-model.number="newCost.int"
type="number" type="number"
@@ -357,23 +437,37 @@ async function handleSubmit() {
/> />
</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">{{
t("purchase_addorder.input_fee")
}}</label>
<input <input
v-model="newCost.cost" v-model="newCost.cost"
type="number" type="number"
class="w-full rounded-lg border bg-white px-3 py-2 text-sm dark:bg-dk-base dark:text-white" class="w-full rounded-lg border bg-white px-3 py-2 text-sm dark:bg-dk-base dark:text-white"
:class="costError ? 'border-red-500' : 'border-gray-300 dark:border-dk-muted'" :class="
costError
? 'border-red-500'
: 'border-gray-300 dark:border-dk-muted'
"
step="0.01" step="0.01"
min="0" 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">{{
t("purchase_addorder.select_currency")
}}</label>
<select <select
v-model="newCost.currencytype" 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" 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"
> >
<option v-for="(label, key) in currencyOptions" :key="key" :value="Number(key)">{{ label }}</option> <option
v-for="(label, key) in currencyOptions"
:key="key"
:value="Number(key)"
>
{{ label }}
</option>
</select> </select>
</div> </div>
<div class="flex items-end"> <div class="flex items-end">
@@ -389,23 +483,50 @@ async function handleSubmit() {
<!-- ==================== 图片上传 ==================== --> <!-- ==================== 图片上传 ==================== -->
<div> <div>
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t("purchase_addorder.upload_photos") }}</label> <label
<useDropzone ref="dropzoneRef" :initialFiles="[]" /> class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300"
>{{ t("purchase_addorder.upload_photos") }}</label
>
<useDropzone
ref="dropzoneRef"
acceptFiles="image/*"
uploadURL="/api/files/upload/image"
:initialFiles="form.photos"
:maxFiles="10"
/>
</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"
>
<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:outline-none focus:ring-2 focus:ring-blue-500/20 disabled:opacity-60" 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:outline-none focus:ring-2 focus:ring-blue-500/20 disabled:opacity-60"
:disabled="loading" :disabled="loading"
@click="handleSubmit" @click="handleSubmit"
> >
<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("message.save_ok") }} {{ t("purchase.submit_changes") }}
</button> </button>
</div> </div>
</div> </div>