diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json
index 0a9d4d5..f6d0675 100644
--- a/.workbuddy/expert-history.json
+++ b/.workbuddy/expert-history.json
@@ -13,5 +13,5 @@
}
]
},
- "lastUpdated": 1776140518763
+ "lastUpdated": 1776144770632
}
\ No newline at end of file
diff --git a/backend/my_work/main.go b/backend/my_work/main.go
index 319b45f..437f86e 100644
--- a/backend/my_work/main.go
+++ b/backend/my_work/main.go
@@ -67,6 +67,7 @@ func main() {
//统一初始化
models.ConfigAllInit()
routers.ApiUserInit() //用户表先初始化这是必须的因为后面需要用到用户组
+ routers.ApiFilesInit()
routers.ApiScheduleInit()
routers.ApiPurchaseInit()
diff --git a/backend/my_work/models/sql.go b/backend/my_work/models/sql.go
index 6d901de..d147681 100644
--- a/backend/my_work/models/sql.go
+++ b/backend/my_work/models/sql.go
@@ -12,19 +12,6 @@ import (
var DB *gorm.DB
-type TabFileInfo_ struct {
- ID uint `gorm:"primaryKey;autoIncrement"`
- Name string `gorm:"not null;size:256;index"` // 前端报告的文件名
- Path string `gorm:"not null;size:300"` //
- Sha256 string `gorm:"not null;size:64;index"` //
- Mime string `gorm:"size:64;index"`
- Type string `gorm:"size:64;index"`
- Const uint `gorm:"default:1;index"`
- Per uint `gorm:"default:1"`
- UserID uint `gorm:"not null;index"`
- Date time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 默认当前时间
-}
-
type TabUser_ struct {
ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键
Name string `gorm:"size:100;uniqueIndex"` // 唯一约束索引
@@ -126,8 +113,6 @@ func DatabaseInit() error {
DB.AutoMigrate(&TabCookie_{})
- DB.AutoMigrate(&TabFileInfo_{})
-
DB.AutoMigrate(&APIRequestLog_{})
return nil
diff --git a/backend/my_work/routers/apiPurchase.go b/backend/my_work/routers/apiPurchase.go
index d212aa8..ca85de9 100644
--- a/backend/my_work/routers/apiPurchase.go
+++ b/backend/my_work/routers/apiPurchase.go
@@ -139,7 +139,7 @@ func ApiPurchase(r *gin.RouterGroup) {
for _, b := range binds {
fileIDs = append(fileIDs, b.FileID)
}
- var files []models.TabFileInfo_
+ var files []TabFileInfo_
if len(fileIDs) > 0 {
models.DB.Where("id IN ?", fileIDs).Find(&files)
}
@@ -448,7 +448,7 @@ func ApiPurchase(r *gin.RouterGroup) {
//绑定文件
for i := 0; i < len(jsondata.Photos); i++ {
- findFile := models.TabFileInfo_{
+ findFile := TabFileInfo_{
Sha256: jsondata.Photos[i],
Type: "image",
}
@@ -588,7 +588,7 @@ func ApiPurchase(r *gin.RouterGroup) {
// 重建图片绑定:先删旧,再插新
models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{})
for _, hash := range from.Photos {
- findFile := models.TabFileInfo_{Sha256: hash, Type: "image"}
+ findFile := TabFileInfo_{Sha256: hash, Type: "image"}
if models.DB.Where(&findFile).First(&findFile).Error == nil {
models.DB.Create(&TabPurchaseFileBind{
OrderID: from.ID,
diff --git a/backend/my_work/routers/api_Files.go b/backend/my_work/routers/api_Files.go
index e0d9a6f..9813ba3 100644
--- a/backend/my_work/routers/api_Files.go
+++ b/backend/my_work/routers/api_Files.go
@@ -6,14 +6,33 @@ import (
"ops/models"
"path"
"path/filepath"
+ "time"
"github.com/gin-gonic/gin"
)
+type TabFileInfo_ struct {
+ ID uint `gorm:"primaryKey;autoIncrement"`
+ Name string `gorm:"not null;size:256;index"` // 前端报告的文件名
+ Path string `gorm:"not null;size:300"` //
+ Sha256 string `gorm:"not null;size:64;index"` //
+ Mime string `gorm:"size:64;index"`
+ Type string `gorm:"size:64;index"`
+ Const uint `gorm:"default:1;index"`
+ Per uint `gorm:"default:1"`
+ UserID uint `gorm:"not null;index"`
+ Date time.Time `gorm:"type:datetime;default:CURRENT_TIMESTAMP"` // 默认当前时间
+}
+
func file_save() {
}
+func ApiFilesInit() {
+
+ models.DB.AutoMigrate(&TabFileInfo_{})
+}
+
func ApiFiles(r *gin.RouterGroup) {
//getfile := r.Group("/get") //定义上传组
@@ -35,7 +54,7 @@ func ApiFiles(r *gin.RouterGroup) {
download = false
}
if isPartOK {
- file_info := models.TabFileInfo_{
+ file_info := TabFileInfo_{
Sha256: hash,
}
if models.DB.Where(&file_info).First(&file_info).Error == nil {
@@ -107,14 +126,14 @@ func ApiFiles(r *gin.RouterGroup) {
}
//记录到数据库
//先检查数据库有没有数据
- fund_file_info := models.TabFileInfo_{
+ fund_file_info := TabFileInfo_{
Name: filename,
Sha256: hash_str,
Mime: mimeType,
Type: "image",
UserID: user.ID,
}
- fund_file_info2 := models.TabFileInfo_{}
+ fund_file_info2 := TabFileInfo_{}
models.DB.Where(&fund_file_info).Find(&fund_file_info2)
diff --git a/backend/my_work/routers/return.go b/backend/my_work/routers/return.go
index d0faa4e..6e69930 100644
--- a/backend/my_work/routers/return.go
+++ b/backend/my_work/routers/return.go
@@ -3,7 +3,6 @@ package routers
import (
"encoding/json"
"fmt"
- "ops/models"
"github.com/gin-gonic/gin"
)
@@ -35,7 +34,7 @@ func ReturnJson(ctx *gin.Context, errMsg string, data map[string]interface{}) {
}
-func ReturnFile(ctx *gin.Context, file_info *models.TabFileInfo_, preview bool) {
+func ReturnFile(ctx *gin.Context, file_info *TabFileInfo_, preview bool) {
if preview {
ctx.File(file_info.Path)
} else {
diff --git a/frontend/ops_vue_js/src/components/PurchaseOrderForm.vue b/frontend/ops_vue_js/src/components/PurchaseOrderForm.vue
index d6a4dad..9b8a1eb 100644
--- a/frontend/ops_vue_js/src/components/PurchaseOrderForm.vue
+++ b/frontend/ops_vue_js/src/components/PurchaseOrderForm.vue
@@ -25,12 +25,7 @@ const props = defineProps({
type: Object,
required: true,
},
- /** 回填费用列表(分为单位) */
- initialCosts: {
- type: Array,
- default: () => [],
- },
- /** 回填图片列表 */
+ /** 回填图片列表 [{ Sha256, Name, ... }] */
initialPhotos: {
type: Array,
default: () => [],
@@ -94,12 +89,12 @@ function removeCostEntry(index) {
/** 将当前 costEntries(元)转换为分并同步到父组件 */
function syncCosts() {
- const converted = costEntries.map((h) => ({
+ // 直接更新父组件的 form._costs,跳过 emit 链路
+ props.modelValue._costs = costEntries.map((h) => ({
...h,
cost: Math.round(h.cost * 100),
costt: Math.round(h.costt * 100),
}));
- emit("update:modelValue", { ...props.modelValue, _costs: converted });
}
watch(
@@ -111,25 +106,24 @@ watch(
},
);
-// 回填费用(分→元)
-watch(
- () => props.initialCosts,
- (list) => {
- if (!list || list.length === 0) return;
- costEntries.splice(0, costEntries.length);
- list.forEach((c) => {
- costEntries.push({
- type: c.costType,
- int: c.quantity,
- cost: parseFloat((c.price / 100).toFixed(2)),
- costt: parseFloat(((c.price * c.quantity) / 100).toFixed(2)),
- currencytype: c.currencyType,
- });
+// ==================== 外部初始化接口 ====================
+/**
+ * 由父组件调用,用于回填已有费用数据(来自 API)
+ * @param {Array} list 费用数组,单位:分
+ */
+function initCostEntries(list) {
+ if (!list || list.length === 0) return;
+ costEntries.splice(0, costEntries.length);
+ list.forEach((c) => {
+ costEntries.push({
+ type: c.type ?? c.CostType ?? 1,
+ int: c.int ?? c.Quantity ?? 1,
+ cost: parseFloat(((c.cost ?? c.Price) / 100).toFixed(2)),
+ costt: parseFloat(((c.costt ?? c.Price * (c.int ?? c.Quantity)) / 100).toFixed(2)),
+ currencytype: c.currencytype ?? c.CurrencyType ?? 1,
});
- syncCosts();
- },
- { immediate: true },
-);
+ });
+}
// ==================== 图片上传 ====================
const photosRef = ref(null);
@@ -141,7 +135,7 @@ function getPhotoHashes() {
return photosRef.value?.return_files().map((f) => f.hash) ?? [];
}
-defineExpose({ getPhotoHashes, costEntries });
+defineExpose({ getPhotoHashes, costEntries, initCostEntries });
// ==================== 表单字段双向绑定 ====================
function update(field, value) {
diff --git a/frontend/ops_vue_js/src/components/useDropzone.vue b/frontend/ops_vue_js/src/components/useDropzone.vue
index 61b38d4..14146b1 100644
--- a/frontend/ops_vue_js/src/components/useDropzone.vue
+++ b/frontend/ops_vue_js/src/components/useDropzone.vue
@@ -53,6 +53,11 @@ const prop = defineProps({
type: String,
default: "/api/files/upload",
},
+ /** 初始已有文件 [{ hash, name, ... }] */
+ initialFiles: {
+ type: Array,
+ default: () => [],
+ },
});
// 初始化 Dropzone
@@ -201,11 +206,43 @@ function return_files() {
return files;
}
+// 加载初始已有文件(编辑场景)
+function loadInitialFiles() {
+ if (!dropzoneInstance || !prop.initialFiles?.length) return;
+ prop.initialFiles.forEach((f) => {
+ // 构造 Dropzone 期望的 mock file 对象
+ const mockFile = {
+ name: f.Name || f.name || f.hash,
+ size: f.Size || f.size || 0,
+ type: f.Mime || f.mime || "image/jpeg",
+ status: Dropzone.SUCCESS,
+ accepted: true,
+ upload: { uuid: f.Hash || f.hash || f.Sha256 },
+ previewElement: null,
+ _removeLink: null,
+ };
+ // 通知 Dropzone "这是一个已存在的文件,不要上传"
+ dropzoneInstance.emit("addedfile", mockFile);
+ dropzoneInstance.emit("complete", mockFile);
+ dropzoneInstance.files.push(mockFile);
+ // 填充上传结果字段
+ const url = `/api/files/get/${f.Hash || f.hash || f.Sha256}`;
+ files.push({
+ uuid: f.Hash || f.hash || f.Sha256,
+ hash: f.Hash || f.hash || f.Sha256,
+ get_url: url,
+ download_url: `/api/files/download/${f.Hash || f.hash || f.Sha256}`,
+ file_name: f.Name || f.name || "",
+ file_size: f.Size || f.size || 0,
+ is_upload: true,
+ });
+ });
+}
+
// 组件挂载时初始化
onMounted(() => {
initDropzone();
-
- //console.log(lightbox)
+ loadInitialFiles();
});
// 组件卸载时销毁
diff --git a/frontend/ops_vue_js/src/i18n/en.json b/frontend/ops_vue_js/src/i18n/en.json
index 5db9cc4..8be64d7 100644
--- a/frontend/ops_vue_js/src/i18n/en.json
+++ b/frontend/ops_vue_js/src/i18n/en.json
@@ -129,6 +129,8 @@
"input_fee": "Fee",
"select_currency": "Select currency",
"add": "Add",
+ "add_cost": "Add Cost",
+ "upload_photos": "Upload Photos",
"other_status": "Other Status",
"update_time": "Update Time",
"tracking_number": "Tracking Number",
diff --git a/frontend/ops_vue_js/src/i18n/zh-CN.json b/frontend/ops_vue_js/src/i18n/zh-CN.json
index dab54da..855a735 100644
--- a/frontend/ops_vue_js/src/i18n/zh-CN.json
+++ b/frontend/ops_vue_js/src/i18n/zh-CN.json
@@ -129,6 +129,8 @@
"input_fee": "费用",
"select_currency": "选择货币类型",
"add": "添加",
+ "add_cost": "添加费用",
+ "upload_photos": "上传图片",
"other_status": "其他状态",
"update_time": "更新时间",
"tracking_number": "快递单号",
diff --git a/frontend/ops_vue_js/src/views/purchase/editorder.vue b/frontend/ops_vue_js/src/views/purchase/editorder.vue
index 3e9a5e3..e67b520 100644
--- a/frontend/ops_vue_js/src/views/purchase/editorder.vue
+++ b/frontend/ops_vue_js/src/views/purchase/editorder.vue
@@ -4,18 +4,19 @@
*
* 功能概述:
* - 通过路由参数 :id 加载已有订单数据
- * - 使用 PurchaseOrderForm 组件展示可编辑表单
+ * - 费用明细直接在本页管理(与 addorder.vue 相同模式)
* - 提交时调用 /purchase/updateorder 保存修改
*/
-import { reactive, ref, onMounted } from "vue";
+import { reactive, ref, computed, watch, onMounted, nextTick } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";
import { useToastStore } from "@/stores/toast";
import { usePageTitle } from "@/composables/usePageTitle";
import { useValidation } from "@/composables";
import { purchaseApi } from "@/api/purchase";
-import PurchaseOrderForm from "@/components/PurchaseOrderForm.vue";
+import tagadder from "@/components/tagadder.vue";
+import useDropzone from "@/components/useDropzone.vue";
usePageTitle("purchase_addorder.edit_order");
@@ -32,24 +33,73 @@ const loading = ref(false);
const pageLoading = ref(true);
const pageError = ref("");
-/** 回填的费用明细(分为单位,传给 PurchaseOrderForm) */
-const initialCosts = ref([]);
-/** 回填的图片列表 */
-const initialPhotos = ref([]);
-
-/** 表单数据 */
+// ==================== 表单数据 ====================
const form = reactive({
title: "",
remark: "",
link: "",
styles: "",
photos: [],
- costs: [],
- _costs: [], // 由 PurchaseOrderForm 组件同步的分为单位费用数组
});
-/** PurchaseOrderForm 组件引用(用于获取图片哈希) */
-const formRef = ref(null);
+// ==================== 费用明细(与 addorder.vue 完全一致) ====================
+const textMaxLen = 256;
+const currencyOptions = { 1: "CNY", 2: "MOP", 3: "HKD", 4: "USD" };
+const costType = computed(() => ({
+ 1: t("cost_type.unit_price"),
+ 2: t("cost_type.freight"),
+}));
+
+/** 已添加的费用列表 */
+const costEntries = reactive([]);
+const costError = ref(false);
+
+const newCost = reactive({
+ type: 1,
+ int: 1,
+ cost: 0,
+ currencytype: 1,
+});
+
+function addCostEntry() {
+ if (!newCost.cost || parseFloat(newCost.cost) <= 0) {
+ costError.value = true;
+ return;
+ }
+ const cost = parseFloat(newCost.cost);
+ costEntries.push({
+ type: newCost.type,
+ int: newCost.int,
+ cost,
+ costt: parseFloat((cost * newCost.int).toFixed(2)),
+ currencytype: newCost.currencytype,
+ });
+ newCost.cost = 0;
+ newCost.type = 1;
+ newCost.int = 1;
+ newCost.currencytype = 1;
+ costError.value = false;
+}
+
+function removeCostEntry(idx) {
+ costEntries.splice(idx, 1);
+}
+
+watch(
+ () => newCost.cost,
+ (val) => {
+ const fixed = parseFloat(val).toFixed(2);
+ if (parseFloat(fixed) !== val) newCost.cost = parseFloat(fixed);
+ if (val > 0) costError.value = false;
+ },
+);
+
+// ==================== 图片上传 ====================
+const dropzoneRef = ref(null);
+
+function getPhotoHashes() {
+ return dropzoneRef.value?.return_files().map((f) => f.hash) ?? [];
+}
// ==================== 加载订单数据 ====================
onMounted(async () => {
@@ -61,14 +111,13 @@ onMounted(async () => {
try {
const res = await purchaseApi.getOrder(orderId);
- console.log(res)
- if (res.errCode !== 0 || res.raw?.err_code !== 0) {
+ if (res.errCode !== 0 || !res.data) {
pageError.value = t("purchase.order_not_found");
pageLoading.value = false;
return;
}
- const { order, costs, photos } = res.raw.data;
+ const { order, costs, photos } = res.data;
// 回填基本信息
form.title = order.Title ?? "";
@@ -76,10 +125,24 @@ onMounted(async () => {
form.link = order.Link ?? "";
form.styles = order.Styles ?? "";
- // 回填费用(传给子组件,由子组件转换为元展示)
- initialCosts.value = costs ?? [];
+ // 回填费用(分→元,直接写 costEntries)
+ if (costs && costs.length > 0) {
+ costs.forEach((c) => {
+ costEntries.push({
+ type: c.CostType,
+ int: c.Quantity,
+ cost: parseFloat((c.Price / 100).toFixed(2)),
+ costt: parseFloat(((c.Price * c.Quantity) / 100).toFixed(2)),
+ currencytype: c.CurrencyType,
+ });
+ });
+ }
+
// 回填图片
- initialPhotos.value = photos ?? [];
+ await nextTick();
+ if (photos && photos.length > 0) {
+ dropzoneRef.value?.loadInitialFiles(photos);
+ }
} catch {
pageError.value = t("purchase.order_not_found");
} finally {
@@ -93,10 +156,15 @@ async function handleSubmit() {
const ok = validate("title", form.title, t("purchase_addorder.title"));
if (!ok) return;
- // 获取图片哈希
- form.photos = formRef.value?.getPhotoHashes() ?? [];
- // 使用子组件同步的费用(分为单位)
- form.costs = form._costs ?? [];
+ form.photos = getPhotoHashes();
+ // 费用(转为分)
+ const rawCosts = costEntries.map((h) => ({
+ type: h.type,
+ int: h.int,
+ cost: Math.round(h.cost * 100),
+ costt: Math.round(h.costt * 100),
+ currencytype: h.currencytype,
+ }));
loading.value = true;
try {
@@ -106,10 +174,10 @@ async function handleSubmit() {
link: form.link,
styles: form.styles,
photos: form.photos,
- costs: form.costs,
+ costs: rawCosts,
});
- if (res.errCode === 0 && res.raw?.err_code === 0) {
+ if (res.errCode === 0) {
toast.success(t("message.save_ok"));
setTimeout(() => {
router.replace(`/purchase/showorder/${orderId}`);
@@ -168,13 +236,163 @@ async function handleSubmit() {
{{ errors.title }}
-
-
| {{ t("purchase_addorder.type") }} | +{{ t("purchase_addorder.quantity") }} | +{{ t("purchase_addorder.fee") }} | +{{ t("purchase_addorder.total_price") }} | +{{ t("purchase_addorder.currency") }} | +{{ t("purchase_addorder.operation") }} | +
|---|---|---|---|---|---|
| + {{ costType[item.type] }} + | +{{ item.int }} | +{{ item.cost }} | +{{ item.costt }} | ++ {{ currencyOptions[item.currencytype] }} + | ++ + | +