up
This commit is contained in:
@@ -13,5 +13,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"lastUpdated": 1776140518763
|
"lastUpdated": 1776144770632
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,7 @@ func main() {
|
|||||||
//统一初始化
|
//统一初始化
|
||||||
models.ConfigAllInit()
|
models.ConfigAllInit()
|
||||||
routers.ApiUserInit() //用户表先初始化这是必须的因为后面需要用到用户组
|
routers.ApiUserInit() //用户表先初始化这是必须的因为后面需要用到用户组
|
||||||
|
routers.ApiFilesInit()
|
||||||
routers.ApiScheduleInit()
|
routers.ApiScheduleInit()
|
||||||
routers.ApiPurchaseInit()
|
routers.ApiPurchaseInit()
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,6 @@ import (
|
|||||||
|
|
||||||
var DB *gorm.DB
|
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 {
|
type TabUser_ struct {
|
||||||
ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键
|
ID uint `gorm:"primaryKey;autoIncrement"` // 自增主键
|
||||||
Name string `gorm:"size:100;uniqueIndex"` // 唯一约束索引
|
Name string `gorm:"size:100;uniqueIndex"` // 唯一约束索引
|
||||||
@@ -126,8 +113,6 @@ func DatabaseInit() error {
|
|||||||
|
|
||||||
DB.AutoMigrate(&TabCookie_{})
|
DB.AutoMigrate(&TabCookie_{})
|
||||||
|
|
||||||
DB.AutoMigrate(&TabFileInfo_{})
|
|
||||||
|
|
||||||
DB.AutoMigrate(&APIRequestLog_{})
|
DB.AutoMigrate(&APIRequestLog_{})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
for _, b := range binds {
|
for _, b := range binds {
|
||||||
fileIDs = append(fileIDs, b.FileID)
|
fileIDs = append(fileIDs, b.FileID)
|
||||||
}
|
}
|
||||||
var files []models.TabFileInfo_
|
var files []TabFileInfo_
|
||||||
if len(fileIDs) > 0 {
|
if len(fileIDs) > 0 {
|
||||||
models.DB.Where("id IN ?", fileIDs).Find(&files)
|
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++ {
|
for i := 0; i < len(jsondata.Photos); i++ {
|
||||||
findFile := models.TabFileInfo_{
|
findFile := TabFileInfo_{
|
||||||
Sha256: jsondata.Photos[i],
|
Sha256: jsondata.Photos[i],
|
||||||
Type: "image",
|
Type: "image",
|
||||||
}
|
}
|
||||||
@@ -588,7 +588,7 @@ func ApiPurchase(r *gin.RouterGroup) {
|
|||||||
// 重建图片绑定:先删旧,再插新
|
// 重建图片绑定:先删旧,再插新
|
||||||
models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{})
|
models.DB.Where("order_id = ?", from.ID).Delete(&TabPurchaseFileBind{})
|
||||||
for _, hash := range from.Photos {
|
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 {
|
if models.DB.Where(&findFile).First(&findFile).Error == nil {
|
||||||
models.DB.Create(&TabPurchaseFileBind{
|
models.DB.Create(&TabPurchaseFileBind{
|
||||||
OrderID: from.ID,
|
OrderID: from.ID,
|
||||||
|
|||||||
@@ -6,14 +6,33 @@ import (
|
|||||||
"ops/models"
|
"ops/models"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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 file_save() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApiFilesInit() {
|
||||||
|
|
||||||
|
models.DB.AutoMigrate(&TabFileInfo_{})
|
||||||
|
}
|
||||||
|
|
||||||
func ApiFiles(r *gin.RouterGroup) {
|
func ApiFiles(r *gin.RouterGroup) {
|
||||||
|
|
||||||
//getfile := r.Group("/get") //定义上传组
|
//getfile := r.Group("/get") //定义上传组
|
||||||
@@ -35,7 +54,7 @@ func ApiFiles(r *gin.RouterGroup) {
|
|||||||
download = false
|
download = false
|
||||||
}
|
}
|
||||||
if isPartOK {
|
if isPartOK {
|
||||||
file_info := models.TabFileInfo_{
|
file_info := TabFileInfo_{
|
||||||
Sha256: hash,
|
Sha256: hash,
|
||||||
}
|
}
|
||||||
if models.DB.Where(&file_info).First(&file_info).Error == nil {
|
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,
|
Name: filename,
|
||||||
Sha256: hash_str,
|
Sha256: hash_str,
|
||||||
Mime: mimeType,
|
Mime: mimeType,
|
||||||
Type: "image",
|
Type: "image",
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
}
|
}
|
||||||
fund_file_info2 := models.TabFileInfo_{}
|
fund_file_info2 := TabFileInfo_{}
|
||||||
|
|
||||||
models.DB.Where(&fund_file_info).Find(&fund_file_info2)
|
models.DB.Where(&fund_file_info).Find(&fund_file_info2)
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package routers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"ops/models"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"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 {
|
if preview {
|
||||||
ctx.File(file_info.Path)
|
ctx.File(file_info.Path)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,12 +25,7 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
/** 回填费用列表(分为单位) */
|
/** 回填图片列表 [{ Sha256, Name, ... }] */
|
||||||
initialCosts: {
|
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
/** 回填图片列表 */
|
|
||||||
initialPhotos: {
|
initialPhotos: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
@@ -94,12 +89,12 @@ function removeCostEntry(index) {
|
|||||||
|
|
||||||
/** 将当前 costEntries(元)转换为分并同步到父组件 */
|
/** 将当前 costEntries(元)转换为分并同步到父组件 */
|
||||||
function syncCosts() {
|
function syncCosts() {
|
||||||
const converted = costEntries.map((h) => ({
|
// 直接更新父组件的 form._costs,跳过 emit 链路
|
||||||
|
props.modelValue._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),
|
||||||
}));
|
}));
|
||||||
emit("update:modelValue", { ...props.modelValue, _costs: converted });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -111,25 +106,24 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// 回填费用(分→元)
|
// ==================== 外部初始化接口 ====================
|
||||||
watch(
|
/**
|
||||||
() => props.initialCosts,
|
* 由父组件调用,用于回填已有费用数据(来自 API)
|
||||||
(list) => {
|
* @param {Array} list 费用数组,单位:分
|
||||||
|
*/
|
||||||
|
function initCostEntries(list) {
|
||||||
if (!list || list.length === 0) return;
|
if (!list || list.length === 0) return;
|
||||||
costEntries.splice(0, costEntries.length);
|
costEntries.splice(0, costEntries.length);
|
||||||
list.forEach((c) => {
|
list.forEach((c) => {
|
||||||
costEntries.push({
|
costEntries.push({
|
||||||
type: c.costType,
|
type: c.type ?? c.CostType ?? 1,
|
||||||
int: c.quantity,
|
int: c.int ?? c.Quantity ?? 1,
|
||||||
cost: parseFloat((c.price / 100).toFixed(2)),
|
cost: parseFloat(((c.cost ?? c.Price) / 100).toFixed(2)),
|
||||||
costt: parseFloat(((c.price * c.quantity) / 100).toFixed(2)),
|
costt: parseFloat(((c.costt ?? c.Price * (c.int ?? c.Quantity)) / 100).toFixed(2)),
|
||||||
currencytype: c.currencyType,
|
currencytype: c.currencytype ?? c.CurrencyType ?? 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
syncCosts();
|
}
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// ==================== 图片上传 ====================
|
// ==================== 图片上传 ====================
|
||||||
const photosRef = ref(null);
|
const photosRef = ref(null);
|
||||||
@@ -141,7 +135,7 @@ function getPhotoHashes() {
|
|||||||
return photosRef.value?.return_files().map((f) => f.hash) ?? [];
|
return photosRef.value?.return_files().map((f) => f.hash) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ getPhotoHashes, costEntries });
|
defineExpose({ getPhotoHashes, costEntries, initCostEntries });
|
||||||
|
|
||||||
// ==================== 表单字段双向绑定 ====================
|
// ==================== 表单字段双向绑定 ====================
|
||||||
function update(field, value) {
|
function update(field, value) {
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ const prop = defineProps({
|
|||||||
type: String,
|
type: String,
|
||||||
default: "/api/files/upload",
|
default: "/api/files/upload",
|
||||||
},
|
},
|
||||||
|
/** 初始已有文件 [{ hash, name, ... }] */
|
||||||
|
initialFiles: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 初始化 Dropzone
|
// 初始化 Dropzone
|
||||||
@@ -201,11 +206,43 @@ function return_files() {
|
|||||||
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(() => {
|
onMounted(() => {
|
||||||
initDropzone();
|
initDropzone();
|
||||||
|
loadInitialFiles();
|
||||||
//console.log(lightbox)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 组件卸载时销毁
|
// 组件卸载时销毁
|
||||||
|
|||||||
@@ -129,6 +129,8 @@
|
|||||||
"input_fee": "Fee",
|
"input_fee": "Fee",
|
||||||
"select_currency": "Select currency",
|
"select_currency": "Select currency",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"add_cost": "Add Cost",
|
||||||
|
"upload_photos": "Upload Photos",
|
||||||
"other_status": "Other Status",
|
"other_status": "Other Status",
|
||||||
"update_time": "Update Time",
|
"update_time": "Update Time",
|
||||||
"tracking_number": "Tracking Number",
|
"tracking_number": "Tracking Number",
|
||||||
|
|||||||
@@ -129,6 +129,8 @@
|
|||||||
"input_fee": "费用",
|
"input_fee": "费用",
|
||||||
"select_currency": "选择货币类型",
|
"select_currency": "选择货币类型",
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
|
"add_cost": "添加费用",
|
||||||
|
"upload_photos": "上传图片",
|
||||||
"other_status": "其他状态",
|
"other_status": "其他状态",
|
||||||
"update_time": "更新时间",
|
"update_time": "更新时间",
|
||||||
"tracking_number": "快递单号",
|
"tracking_number": "快递单号",
|
||||||
|
|||||||
@@ -4,18 +4,19 @@
|
|||||||
*
|
*
|
||||||
* 功能概述:
|
* 功能概述:
|
||||||
* - 通过路由参数 :id 加载已有订单数据
|
* - 通过路由参数 :id 加载已有订单数据
|
||||||
* - 使用 PurchaseOrderForm 组件展示可编辑表单
|
* - 费用明细直接在本页管理(与 addorder.vue 相同模式)
|
||||||
* - 提交时调用 /purchase/updateorder 保存修改
|
* - 提交时调用 /purchase/updateorder 保存修改
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { reactive, ref, onMounted } from "vue";
|
import { reactive, ref, computed, watch, onMounted, nextTick } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
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 PurchaseOrderForm from "@/components/PurchaseOrderForm.vue";
|
import tagadder from "@/components/tagadder.vue";
|
||||||
|
import useDropzone from "@/components/useDropzone.vue";
|
||||||
|
|
||||||
usePageTitle("purchase_addorder.edit_order");
|
usePageTitle("purchase_addorder.edit_order");
|
||||||
|
|
||||||
@@ -32,24 +33,73 @@ const loading = ref(false);
|
|||||||
const pageLoading = ref(true);
|
const pageLoading = ref(true);
|
||||||
const pageError = ref("");
|
const pageError = ref("");
|
||||||
|
|
||||||
/** 回填的费用明细(分为单位,传给 PurchaseOrderForm) */
|
// ==================== 表单数据 ====================
|
||||||
const initialCosts = ref([]);
|
|
||||||
/** 回填的图片列表 */
|
|
||||||
const initialPhotos = ref([]);
|
|
||||||
|
|
||||||
/** 表单数据 */
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
title: "",
|
title: "",
|
||||||
remark: "",
|
remark: "",
|
||||||
link: "",
|
link: "",
|
||||||
styles: "",
|
styles: "",
|
||||||
photos: [],
|
photos: [],
|
||||||
costs: [],
|
|
||||||
_costs: [], // 由 PurchaseOrderForm 组件同步的分为单位费用数组
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** PurchaseOrderForm 组件引用(用于获取图片哈希) */
|
// ==================== 费用明细(与 addorder.vue 完全一致) ====================
|
||||||
const formRef = ref(null);
|
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 () => {
|
onMounted(async () => {
|
||||||
@@ -61,14 +111,13 @@ onMounted(async () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await purchaseApi.getOrder(orderId);
|
const res = await purchaseApi.getOrder(orderId);
|
||||||
console.log(res)
|
if (res.errCode !== 0 || !res.data) {
|
||||||
if (res.errCode !== 0 || res.raw?.err_code !== 0) {
|
|
||||||
pageError.value = t("purchase.order_not_found");
|
pageError.value = t("purchase.order_not_found");
|
||||||
pageLoading.value = false;
|
pageLoading.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { order, costs, photos } = res.raw.data;
|
const { order, costs, photos } = res.data;
|
||||||
|
|
||||||
// 回填基本信息
|
// 回填基本信息
|
||||||
form.title = order.Title ?? "";
|
form.title = order.Title ?? "";
|
||||||
@@ -76,10 +125,24 @@ onMounted(async () => {
|
|||||||
form.link = order.Link ?? "";
|
form.link = order.Link ?? "";
|
||||||
form.styles = order.Styles ?? "";
|
form.styles = order.Styles ?? "";
|
||||||
|
|
||||||
// 回填费用(传给子组件,由子组件转换为元展示)
|
// 回填费用(分→元,直接写 costEntries)
|
||||||
initialCosts.value = costs ?? [];
|
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 {
|
} catch {
|
||||||
pageError.value = t("purchase.order_not_found");
|
pageError.value = t("purchase.order_not_found");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -93,10 +156,15 @@ async function handleSubmit() {
|
|||||||
const ok = validate("title", form.title, t("purchase_addorder.title"));
|
const ok = validate("title", form.title, t("purchase_addorder.title"));
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
|
||||||
// 获取图片哈希
|
form.photos = getPhotoHashes();
|
||||||
form.photos = formRef.value?.getPhotoHashes() ?? [];
|
// 费用(转为分)
|
||||||
// 使用子组件同步的费用(分为单位)
|
const rawCosts = costEntries.map((h) => ({
|
||||||
form.costs = form._costs ?? [];
|
type: h.type,
|
||||||
|
int: h.int,
|
||||||
|
cost: Math.round(h.cost * 100),
|
||||||
|
costt: Math.round(h.costt * 100),
|
||||||
|
currencytype: h.currencytype,
|
||||||
|
}));
|
||||||
|
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@@ -106,10 +174,10 @@ async function handleSubmit() {
|
|||||||
link: form.link,
|
link: form.link,
|
||||||
styles: form.styles,
|
styles: form.styles,
|
||||||
photos: form.photos,
|
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"));
|
toast.success(t("message.save_ok"));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
router.replace(`/purchase/showorder/${orderId}`);
|
router.replace(`/purchase/showorder/${orderId}`);
|
||||||
@@ -168,13 +236,163 @@ async function handleSubmit() {
|
|||||||
{{ errors.title }}
|
{{ errors.title }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 表单主体(公共组件) -->
|
<!-- ==================== 订单信息区块 ==================== -->
|
||||||
<PurchaseOrderForm
|
<div class="border-b border-gray-200 px-6 py-4 dark:border-dk-muted">
|
||||||
v-model="form"
|
<h4 class="text-sm font-semibold text-gray-900 dark:text-white">
|
||||||
:initialCosts="initialCosts"
|
{{ t("purchase_addorder.order_info") }}
|
||||||
:initialPhotos="initialPhotos"
|
</h4>
|
||||||
ref="formRef"
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 px-6 py-5">
|
||||||
|
<!-- 标题字段(必填) -->
|
||||||
|
<div>
|
||||||
|
<label 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>
|
||||||
|
<input
|
||||||
|
v-model="form.title"
|
||||||
|
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="errors.title ? 'border-red-500' : 'border-gray-300'"
|
||||||
|
:placeholder="t('purchase_addorder.part_name')"
|
||||||
/>
|
/>
|
||||||
|
<span v-if="errors.title" class="mt-1 block text-xs text-red-500">{{ errors.title }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 备注字段 -->
|
||||||
|
<div>
|
||||||
|
<label 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>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
v-model="form.remark"
|
||||||
|
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"
|
||||||
|
rows="4"
|
||||||
|
:placeholder="t('purchase_addorder.remarks_text')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 采购链接 -->
|
||||||
|
<div>
|
||||||
|
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">{{ t("purchase_addorder.link") }}</label>
|
||||||
|
<textarea
|
||||||
|
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"
|
||||||
|
rows="2"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
<tagadder :placeholder="t('purchase_addorder.add_style')" v-model="form.styles" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ==================== 费用明细表格 ==================== -->
|
||||||
|
<div>
|
||||||
|
<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">
|
||||||
|
<table class="w-full text-left text-sm text-gray-900">
|
||||||
|
<thead>
|
||||||
|
<tr 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.type") }}</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>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr
|
||||||
|
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.cost }}</td>
|
||||||
|
<td class="px-3 py-2 text-gray-500">{{ item.costt }}</td>
|
||||||
|
<td class="px-3 py-2 text-gray-500">
|
||||||
|
{{ currencyOptions[item.currencytype] }}
|
||||||
|
</td>
|
||||||
|
<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>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加费用表单 -->
|
||||||
|
<div class="grid grid-cols-2 gap-4 sm:grid-cols-5">
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-xs font-medium text-gray-500">{{ 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"
|
||||||
|
>
|
||||||
|
<option v-for="(label, key) in costType" :key="key" :value="Number(key)">{{ label }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-xs font-medium text-gray-500">{{ 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>
|
||||||
|
<label class="mb-1 block text-xs font-medium text-gray-500">{{ t("purchase_addorder.input_fee") }}</label>
|
||||||
|
<input
|
||||||
|
v-model="newCost.cost"
|
||||||
|
type="number"
|
||||||
|
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'"
|
||||||
|
step="0.01"
|
||||||
|
min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="mb-1 block text-xs font-medium text-gray-500">{{ 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"
|
||||||
|
>
|
||||||
|
<option v-for="(label, key) in currencyOptions" :key="key" :value="Number(key)">{{ label }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-end">
|
||||||
|
<button
|
||||||
|
class="w-full rounded-lg border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-100 dark:border-dk-muted dark:bg-dk-base dark:text-gray-200 dark:hover:bg-dk-muted"
|
||||||
|
@click="addCostEntry"
|
||||||
|
>
|
||||||
|
{{ t("purchase_addorder.add_cost") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
<useDropzone ref="dropzoneRef" :initialFiles="[]" />
|
||||||
|
</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">
|
||||||
|
|||||||
Reference in New Issue
Block a user