diff --git a/.env.production b/.env.production
index e097007..b8c26cc 100644
--- a/.env.production
+++ b/.env.production
@@ -1,5 +1,5 @@
# 线上环境接口地址
-VITE_API_URL = "/pro"
+VITE_API_URL = "/api"
# 静态文件获取根路径
VITE_PUBLIC_PATH = "/"
diff --git a/package.json b/package.json
index 20d2f29..8aa0324 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "teek-design-vue3-template",
+ "name": "erp-frontend",
"version": "2.0.0",
"private": true,
"description": "牛安后台管理系统",
diff --git a/src/common/api/user.ts b/src/common/api/user.ts
index ab6c24e..d86b611 100644
--- a/src/common/api/user.ts
+++ b/src/common/api/user.ts
@@ -21,7 +21,7 @@ export const UserService = {
},
checkIsLogined() {
- return get("/open/check-login");
+ return get("/auth/check-login");
},
/**
@@ -29,12 +29,12 @@ export const UserService = {
* 用于前端页面刷新后检测当前token是否有效
*/
checkLogin() {
- return get("/open/check-login");
+ return get("/auth/check-login");
},
// 获取用户信息
getUserInfo() {
- return get("/auth/getUserInfo");
+ return get("/auth/check-login");
},
getDynamicRouter() {
diff --git a/src/common/http/request.ts b/src/common/http/request.ts
index b661ef5..275f6bd 100644
--- a/src/common/http/request.ts
+++ b/src/common/http/request.ts
@@ -2,6 +2,7 @@ import { useUserStore } from "@/pinia";
import axios from "axios";
import type { AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { ElMessage } from "element-plus";
+import { $t } from "@/common/languages";
// 创建 axios 实例
const service: AxiosInstance = axios.create({
@@ -24,7 +25,7 @@ service.interceptors.request.use(
return config;
},
error => {
- ElMessage.error("请求发送失败");
+ ElMessage.error($t("_http.request_failed"));
return Promise.reject(error);
}
);
@@ -38,31 +39,34 @@ service.interceptors.response.use(
// 通常 code === 0 表示成功
if (res.code === 0) {
return res as any; // 直接返回业务数据
+ } else if (res.code === 3) {
+ ElMessage.error(res.msg || $t("_http.invalid_credentials"));
+ return Promise.reject(new Error(res.msg || $t("_http.invalid_credentials")));
} else {
- ElMessage.error(res.msg || "请求失败");
+ ElMessage.error(res.msg || $t("_http.request_error"));
return Promise.reject(new Error(res.msg || "Error"));
}
},
error => {
// 处理网络错误或 HTTP 状态码非 2xx 的情况
- let message = "网络异常";
+ let message = $t("_http.network_error");
const { isLogined } = storeToRefs(useUserStore());
if (error.response) {
const status = error.response.status;
switch (status) {
case 401:
- message = "未登录或会话已过期,请重新登录";
+ message = $t("_http.unauthorized");
isLogined.value = false;
useRouter().push("/login");
break;
case 403:
- message = "权限不足";
+ message = $t("_http.forbidden");
break;
case 500:
- message = "服务器内部错误";
+ message = $t("_http.server_error");
break;
default:
- message = `请求失败 ( $ {status})`;
+ message = $t("_http.request_failed_status", { status });
}
}
ElMessage.error(message);
diff --git a/src/common/languages/locales/en-US.ts b/src/common/languages/locales/en-US.ts
index 1d2ade0..6dc766e 100644
--- a/src/common/languages/locales/en-US.ts
+++ b/src/common/languages/locales/en-US.ts
@@ -729,6 +729,12 @@ export default {
generate_order_error: "Generate Failed",
vendor_priority_tip: "Priority",
},
+ purchase_plan_item: {
+ input_partNumber: "Please enter part number",
+ input_purchaseCount: "Please enter plan count",
+ input_price: "Please enter price",
+ input_currentCount: "Please enter current count",
+ },
purchaseorder: {
delete_message: "Delete Order",
part_number_not_exists: "Part number does not exist",
@@ -917,9 +923,11 @@ export default {
tableTitle: "Purchase Details",
templateFileName: "Purchase Plan Template",
},
- purchaseorder: {
+ purchase_order: {
add: "Add Purchase Order",
edit: "Edit Purchase Order",
+ inbound: "Purchase Inbound",
+ qrcode: "Print QR Code",
showItem: "Order Items",
baseTitle: "Purchase Order Basic Info",
tableTitle: "Order Details",
@@ -1235,5 +1243,16 @@ export default {
_status: {
in_progress: "In Progress",
completed: "Completed",
+ receipting: "Receipting",
+ },
+ _http: {
+ request_failed: "Request failed",
+ invalid_credentials: "Invalid username or password",
+ request_error: "Request error",
+ network_error: "Network error",
+ unauthorized: "Not logged in or session expired, please log in again",
+ forbidden: "Permission denied",
+ server_error: "Server internal error",
+ request_failed_status: "Request failed ({status})",
},
};
diff --git a/src/common/languages/locales/zh-CN.ts b/src/common/languages/locales/zh-CN.ts
index ad0bf9d..01fbb85 100644
--- a/src/common/languages/locales/zh-CN.ts
+++ b/src/common/languages/locales/zh-CN.ts
@@ -760,6 +760,12 @@ export default {
generate_order_error: "生成采购订单失败",
vendor_priority_tip: "优先",
},
+ purchase_plan_item: {
+ input_partNumber: "请输入物料编号",
+ input_purchaseCount: "请输入计划数量",
+ input_price: "请输入单价",
+ input_currentCount: "请输入本次采购数量",
+ },
purchase_order: {
delete_message: "删除采购订单",
input_vendorName: "请选择供应商",
@@ -1008,6 +1014,9 @@ export default {
add: "新建采购计划",
edit: "编辑采购计划",
generateOrder: "生成采购订单",
+ showItem: "采购明细",
+ baseTitle: "采购计划基本信息",
+ tableTitle: "采购明细",
model: "型号",
defaultVendor: "默认供应商",
demandQuantity: "需求量",
@@ -1023,6 +1032,7 @@ export default {
edit: "编辑采购订单",
inbound: "采购入库",
qrcode: "二维码打印",
+ showItem: "采购明细",
baseTitle: "采购订单基本信息",
tableTitle: "采购明细",
templateFileName: "采购订单模板",
@@ -1337,5 +1347,16 @@ export default {
_status: {
in_progress: "入库中",
completed: "已完成",
+ receipting: "入库中",
+ },
+ _http: {
+ request_failed: "请求发送失败",
+ invalid_credentials: "用户名或密码错误",
+ request_error: "请求失败",
+ network_error: "网络异常",
+ unauthorized: "未登录或会话已过期,请重新登录",
+ forbidden: "权限不足",
+ server_error: "服务器内部错误",
+ request_failed_status: "请求失败 ({status})",
},
};
diff --git a/src/components/base/base-qrcode/QrCodeDialog.vue b/src/components/base/base-qrcode/QrCodeDialog.vue
index c63ca76..98f5a4e 100644
--- a/src/components/base/base-qrcode/QrCodeDialog.vue
+++ b/src/components/base/base-qrcode/QrCodeDialog.vue
@@ -218,6 +218,7 @@ watch(visible, newVal => {
width="450px"
:close-on-click-modal="false"
:close-on-press-escape="false"
+ :lock-scroll="false"
>
diff --git a/src/pinia/stores/core/user.ts b/src/pinia/stores/core/user.ts
index 1f9a75a..be133ce 100644
--- a/src/pinia/stores/core/user.ts
+++ b/src/pinia/stores/core/user.ts
@@ -59,33 +59,34 @@ export const useUserStore = defineStore(
};
const getUserInfo = async () => {
- // 模拟获取用户信息
- // return await UserService.getUserInfo().then(res => {
- // setRoles(res.data.roles);
- // setUserInfo(res.data);
- // return res.data;
- // });
-
- const userInfo = await new Promise
(resolve => {
- setTimeout(() => {
- resolve({
- userId: "v10001",
- username: "Admin",
- sex: "保密",
- signature: "这个人很懒,什么都没有写",
- email: "1234567890@qq.com",
- phone: "1234567890",
- avatar: "https://cdn.jsdelivr.net/gh/Kele-Bingtang/static/user/avatar1.png",
- roles: ["admin"],
- job: "开发工程师",
- dept: "Teek 云科技技术部 - 智能全栈科",
- registerTime: "2022-10-01 19:07:27",
- });
- }, 500);
+ return await UserService.getUserInfo().then(res => {
+ setRoles(res.data.roles);
+ setUserInfo(res.data.userInfo);
+ return res.data.userInfo;
});
- setUserInfo(userInfo);
- return userInfo;
+ // return {};
+
+ // const userInfo = await new Promise(resolve => {
+ // setTimeout(() => {
+ // resolve({
+ // userId: "v10001",
+ // username: "Admin",
+ // sex: "保密",
+ // signature: "这个人很懒,什么都没有写",
+ // email: "1234567890@qq.com",
+ // phone: "1234567890",
+ // avatar: "https://cdn.jsdelivr.net/gh/Kele-Bingtang/static/user/avatar1.png",
+ // roles: ["admin"],
+ // job: "开发工程师",
+ // dept: "Teek 云科技技术部 - 智能全栈科",
+ // registerTime: "2022-10-01 19:07:27",
+ // });
+ // }, 500);
+ // });
+
+ // setUserInfo(userInfo);
+ // return userInfo;
};
/**
diff --git a/src/views/core/login/loginForm.vue b/src/views/core/login/loginForm.vue
index adb6d9d..dd4de70 100644
--- a/src/views/core/login/loginForm.vue
+++ b/src/views/core/login/loginForm.vue
@@ -53,16 +53,16 @@ const login = () => {
loading.value = true;
try {
// 执行登录
- await userStore.login({ ...loginForm });
- // if (!result) {
- // ElNotification({
- // title: getTimeState(),
- // message: "登录失败,用户名或密码错误",
- // type: "success",
- // duration: 3000,
- // });
- // return;
- // }
+ const result = await userStore.login({ ...loginForm });
+ if (!result) {
+ ElNotification({
+ title: getTimeState(),
+ message: "登录失败,用户名或密码错误",
+ type: "success",
+ duration: 3000,
+ });
+ return;
+ }
// 跳转到首页或者 URL 携带的 redirect 页(优先级高)
let path = HOME_URL;
diff --git a/src/views/production/bom/BomView.vue b/src/views/production/bom/BomView.vue
index 984f3e0..fba575e 100644
--- a/src/views/production/bom/BomView.vue
+++ b/src/views/production/bom/BomView.vue
@@ -130,7 +130,7 @@ const operateButtonClick = (eventName: string, row: any) => {
const mappingConfig: FieldMappingConfig = {
partNumber: {
- sourceKey: ["商品编号", "partNumber"],
+ sourceKey: ["编号", "物料编号", "商品编号", "partNumber"],
header: $t("_prop.production.bom_item.partNumber"),
width: 20,
defaultValue: "",
diff --git a/src/views/production/finished-product-shipment/FinishedProductShipmentView.vue b/src/views/production/finished-product-shipment/FinishedProductShipmentView.vue
index 1f7f0c2..d296a20 100644
--- a/src/views/production/finished-product-shipment/FinishedProductShipmentView.vue
+++ b/src/views/production/finished-product-shipment/FinishedProductShipmentView.vue
@@ -238,6 +238,8 @@ const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
if (row.status === 1 && button.eventName === "approve") return false;
// 已审核后不能编辑和删除
if (row.status === 1 && (button.eventName === "edit" || button.eventName === "remove")) return false;
+ // 退料按钮只在已审核状态下展示
+ if (button.eventName === "productionReturn" && row.status !== 1) return false;
return true;
};
diff --git a/src/views/purchase/purchase-order/PurchaseOrderView.vue b/src/views/purchase/purchase-order/PurchaseOrderView.vue
index 15eb475..d39ceea 100644
--- a/src/views/purchase/purchase-order/PurchaseOrderView.vue
+++ b/src/views/purchase/purchase-order/PurchaseOrderView.vue
@@ -7,6 +7,7 @@ import { $t } from "@/common/languages";
import { ElMessage, ElMessageBox, type FormInstance, type FormItemRule, type FormRules } from "element-plus";
import BaseFormWithTable from "@/components/base/base-form-with-table/BaseFormWithTable.vue";
import BaseTableForm from "@/components/base/base-table-form/BaseTableForm.vue";
+import BaseItemDialog from "@/components/base/base-item-dialog/BaseItemDialog.vue";
import ExpandablePageableTable from "@/components/base/expandable-pageable-table/ExpandablePageableTable.vue";
import BaseSelect from "@/components/base/base-select/BaseSelect.vue";
import QrCodeDialog from "@/components/base/base-qrcode/QrCodeDialog.vue";
@@ -29,12 +30,14 @@ const partNumberExistsUrl = "/warehouse/warehouseitem/existsWarehouseItem";
const searchers = [
{ name: "searchCode", type: "text" as const, placeholder: $t("_prop.purchase.purchase_order.searchCode") },
+ { name: "partNumber", type: "text" as const, placeholder: $t("_prop.purchase.purchase_order_item.partNumber") },
];
const getFormStatusLabel = (status: number) => {
const labels: Record = {
0: $t("_status.in_progress"),
4: $t("_status.completed"),
+ 5: $t("_status.receipting"),
};
return labels[status] ?? String(status);
};
@@ -60,7 +63,7 @@ const validatePartNumber = (rule: any, value: string, callback: (error?: Error)
};
const rules = reactive({
- vendorName: [{ required: true, message: $t("_message.purchase.purchase_order.input_vendorName"), trigger: "blur" }],
+ vendorNo: [{ required: true, message: $t("_message.purchase.purchase_order.select_vendor"), trigger: "change" }],
partNumber: [
{
required: true,
@@ -97,7 +100,7 @@ const inboundForm = reactive({
orderCode: "",
vendorName: "",
formCode: "",
- storeNo: null as number,
+ storeNo: null as number | null,
storeName: "",
formMark: "",
items: [] as any[],
@@ -106,6 +109,10 @@ const inboundForm = reactive({
const qrCodeVisible = ref(false);
const qrCodeData = ref([]);
+const showItemVisible = ref(false);
+const showItemData = ref([]);
+const showItemRow = ref(null);
+
const mappingConfig: FieldMappingConfig = {
partNumber: {
sourceKey: "物料编号",
@@ -266,6 +273,11 @@ const showQrCode = async (row: any) => {
}
};
+const showItem = (row: any) => {
+ showItemRow.value = row;
+ showItemVisible.value = true;
+};
+
const submit = (formData: any, formRef: FormInstance | undefined) => {
if (formRef !== undefined) {
formRef.validate(valid => {
@@ -307,6 +319,9 @@ const operateButtonClick = (eventName: string, row: any) => {
case "printQrCode":
showQrCode(row);
break;
+ case "showItem":
+ showItem(row);
+ break;
}
};
@@ -368,6 +383,7 @@ const getFormStatusTagType = (code: number | null): string => {
:item-url="getItemsUrl"
item-id-key="id"
item-id-name="orderId"
+ item-field-name="items"
>
@@ -434,9 +450,9 @@ const getFormStatusTagType = (code: number | null): string => {
-
+
{
:qr-code-list="qrCodeData"
:qr-code-size="200"
/>
+
+
+
+
+
+
+
+
+
+
+ {{ new Decimal(row.price ?? 0).toFixed(2) }}
+
+
+
+
+ {{ new Decimal(row.totalPrice ?? 0).toFixed(2) }}
+
+
+
+
+
diff --git a/src/views/purchase/purchase-plan/PurchasePlanView.vue b/src/views/purchase/purchase-plan/PurchasePlanView.vue
index 1ad448e..5b91603 100644
--- a/src/views/purchase/purchase-plan/PurchasePlanView.vue
+++ b/src/views/purchase/purchase-plan/PurchasePlanView.vue
@@ -30,6 +30,7 @@ const removeUrl = "/purchase/purchaseplan/deletePurchasePlan";
const getItemsUrl = "/purchase/purchaseplan/getPurchasePlanItemsWithVendorSuggestions";
const generateOrderUrl = "/purchase/purchaseplan/generatePurchaseOrder";
const getItemUrl = "/purchase/purchaseplan/getPurchasePlanItems";
+const partNumberExistsUrl = "/warehouse/warehouseitem/existsWarehouseItem";
const searchers = [
{ name: "searchCode", type: "text" as const, placeholder: $t("_prop.purchase.purchase_plan.searchCode") },
];
@@ -53,6 +54,28 @@ const rules = reactive({
],
});
const itemArrayName = "planItems";
+
+/**
+ * 验证物料编号是否存在
+ */
+const validatePartNumber = (rule: any, value: string, callback: (error?: Error) => void) => {
+ if (!value) {
+ callback(new Error($t("_message.purchase.purchase_plan_item.input_partNumber")));
+ return;
+ }
+ get(partNumberExistsUrl, { partNumber: value })
+ .then(res => {
+ if (res.data === true) {
+ callback();
+ } else {
+ callback(new Error($t("_message.warehouse.warehouse_item.not_exist_partNumber")));
+ }
+ })
+ .catch(() => {
+ callback();
+ });
+};
+
/**
* 基本不变通用变量
*/
@@ -445,8 +468,6 @@ const mappingConfig: FieldMappingConfig = {
@@ -455,9 +476,7 @@ const mappingConfig: FieldMappingConfig = {
@@ -477,9 +496,7 @@ const mappingConfig: FieldMappingConfig = {
diff --git a/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue b/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue
index 9b61b64..1314f31 100644
--- a/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue
+++ b/src/views/warehouse/stock-transfer-order/StockTransferOrderView.vue
@@ -294,6 +294,8 @@ const operateButtonClick = (eventName: string, row: any) => {
const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
if (row.status === 0 && button.eventName === "reject") return false;
if (row.status === 1 && button.eventName === "approve") return false;
+ if (row.status === 1 && button.eventName === "edit") return false;
+ if (row.status === 1 && button.eventName === "remove") return false;
return true;
};
@@ -311,12 +313,12 @@ const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
-
-
-
-
-
-
+
+
+
+
+
+
{
const validVendors = dialogTableData.value.filter(v => v.vendorId !== null && v.vendorId !== undefined);
- if (validVendors.length === 0) {
- return;
- }
-
const vendorList = validVendors.map(v => ({
vendorId: v.vendorId,
costPrice: v.costPrice,
@@ -244,14 +239,18 @@ const cancelVendorEdit = (row: any, originalRow: any) => {
-
-
-
-
-
-
+
+
+
+
+
+
-
+
@@ -325,7 +324,14 @@ const cancelVendorEdit = (row: any, originalRow: any) => {
-
+
diff --git a/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue b/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue
index d65b7dd..efca874 100644
--- a/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue
+++ b/src/views/warehouse/warehouse-receipt/WarehouseReceiptView.vue
@@ -161,6 +161,7 @@ const authShowFunc = (row: any, button: globalThis.ButtonProp) => {
if (row.status === 0 && button.eventName === "reject") return false;
if (row.status === 1 && button.eventName === "approve") return false;
if (row.status === 1 && button.eventName === "edit") return false;
+ if (row.status === 1 && button.eventName === "remove") return false;
return true;
};
@@ -212,11 +213,11 @@ const submit = (form: any, formRef: FormInstance | undefined) => {
-
-
-
-
-
+
+
+
+
+
{
open: viteEnv.VITE_OPEN,
cors: true,
// 跨域代理配置
- proxy: {
- "/api": {
- target: "https://vue3-design.teek.top",
- changeOrigin: true,
- secure: true, // 是否忽略 https 安全证书问题,true 不忽略,false 忽略
- rewrite: path => path.replace(/^\/api/, ""),
- },
- },
+ // proxy: {
+ // "/api": {
+ // target: "https://vue3-design.teek.top",
+ // changeOrigin: true,
+ // secure: true, // 是否忽略 https 安全证书问题,true 不忽略,false 忽略
+ // rewrite: path => path.replace(/^\/api/, ""),
+ // },
+ // },
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: {
clientFiles: ["./index.html", "./src/{views,components}/*"],