@@ -10,21 +10,50 @@
+
+
diff --git a/src/components/common/PageTabBar.vue b/src/components/common/PageTabBar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..aafb94b68ee5a52d757251553bffbc3df7e562cc
--- /dev/null
+++ b/src/components/common/PageTabBar.vue
@@ -0,0 +1,205 @@
+
+
+
+
+
+ {{ resolveTabLabel(tab) }}
+
+
+
+
+
+ {{ $t("message.closeTab") }}
+
+
+ {{ $t("message.closeLeftTabs") }}
+
+
+ {{ $t("message.closeRightTabs") }}
+
+
+ {{ $t("message.closeOtherTabs") }}
+
+
+ {{ $t("message.closeAllTabs") }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/composables/useModalForm.js b/src/composables/useModalForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..e95958d371b03fd195d24b6cefec830d100dc662
--- /dev/null
+++ b/src/composables/useModalForm.js
@@ -0,0 +1,45 @@
+import { computed, unref } from "vue";
+
+const isEmptyValue = (value) => {
+ if (value === null || value === undefined) {
+ return true;
+ }
+ if (typeof value === "string") {
+ return value.trim() === "";
+ }
+ if (Array.isArray(value)) {
+ return value.length === 0;
+ }
+ return false;
+};
+
+export default function useModalForm(form, rules) {
+ const resolvedRules = computed(() => {
+ const rawRules = unref(rules);
+ return rawRules || {};
+ });
+
+ const requiredFields = computed(() =>
+ Object.keys(resolvedRules.value).filter(
+ (field) =>
+ Array.isArray(resolvedRules.value[field]) &&
+ resolvedRules.value[field].some(
+ (rule) => rule && rule.required === true,
+ ),
+ ),
+ );
+
+ const isFormValid = computed(() =>
+ requiredFields.value.every((field) => {
+ const value = form[field];
+ if (value === 0) {
+ return true;
+ }
+ return !isEmptyValue(value);
+ }),
+ );
+
+ return {
+ isFormValid,
+ };
+}
diff --git a/src/composables/useTabs.js b/src/composables/useTabs.js
new file mode 100644
index 0000000000000000000000000000000000000000..1eac9166517bc317ea5c7829bf3103b83532fd77
--- /dev/null
+++ b/src/composables/useTabs.js
@@ -0,0 +1,207 @@
+import { ref, computed, provide, inject, watch } from "vue";
+import { useRouter } from "vue-router";
+import { getPageTitle } from "@/utils/pageTitle";
+
+const HOME_TAB = {
+ path: "/home",
+ title: "message.home",
+ closable: false,
+};
+
+export const TABS_CONTEXT_KEY = Symbol("TABS_CONTEXT_KEY");
+const TABS_STORAGE_KEY = "app-tabs-state";
+
+const getStoredTabs = () => {
+ try {
+ const stored = sessionStorage.getItem(TABS_STORAGE_KEY);
+ if (stored) {
+ const { tabs: storedTabs, activeTab } = JSON.parse(stored);
+ if (Array.isArray(storedTabs) && storedTabs.length > 0) {
+ return { tabs: storedTabs, activeTab };
+ }
+ }
+ } catch (error) {}
+ return null;
+};
+
+const saveTabsState = (tabs, activeTab) => {
+ try {
+ sessionStorage.setItem(
+ TABS_STORAGE_KEY,
+ JSON.stringify({ tabs, activeTab }),
+ );
+ } catch (error) {}
+};
+
+export const clearSavedTabs = () => {
+ try {
+ sessionStorage.removeItem(TABS_STORAGE_KEY);
+ } catch (error) {}
+};
+
+export function createTabsContext(i18n) {
+ const router = useRouter();
+
+ const storedState = getStoredTabs();
+ const tabs = ref(storedState?.tabs || [{ ...HOME_TAB }]);
+ const activeTab = ref(storedState?.activeTab || "/home");
+
+ const getTabTitle = (path) => {
+ const key = getPageTitle(path);
+ if (key && i18n) {
+ const translated = i18n.t(key);
+ if (translated !== key) {
+ return translated;
+ }
+ }
+ return key || path;
+ };
+
+ const addTab = (tabInfo) => {
+ const { path, title, name, closable = true } = tabInfo;
+ if (!path) return;
+
+ const existing = tabs.value.find((tab) => tab.path === path);
+ if (existing) {
+ activeTab.value = path;
+ return;
+ }
+
+ const tabTitle = title || getTabTitle(path);
+ tabs.value.push({
+ path,
+ title: tabTitle,
+ name,
+ closable,
+ });
+ activeTab.value = path;
+ };
+
+ const removeTab = (targetPath) => {
+ if (targetPath === HOME_TAB.path) return;
+
+ const index = tabs.value.findIndex((tab) => tab.path === targetPath);
+ if (index === -1) return;
+
+ const wasActive = activeTab.value === targetPath;
+ tabs.value.splice(index, 1);
+
+ if (wasActive) {
+ const nextTab = tabs.value[index] || tabs.value[index - 1];
+ if (nextTab) {
+ activeTab.value = nextTab.path;
+ router.push(nextTab.path);
+ }
+ }
+ };
+
+ const closeLeftTabs = (targetPath) => {
+ const index = tabs.value.findIndex((tab) => tab.path === targetPath);
+ if (index <= 0) return;
+
+ const toRemove = [];
+ for (let i = 0; i < index; i++) {
+ if (tabs.value[i].closable !== false) {
+ toRemove.push(tabs.value[i].path);
+ }
+ }
+
+ tabs.value = tabs.value.filter(
+ (tab) => !toRemove.includes(tab.path) || tab.path === targetPath,
+ );
+
+ const currentIdx = tabs.value.findIndex(
+ (tab) => tab.path === activeTab.value,
+ );
+ if (currentIdx === -1) {
+ const next = tabs.value[index - 1] || tabs.value[0];
+ if (next) {
+ activeTab.value = next.path;
+ router.push(next.path);
+ }
+ }
+ };
+
+ const closeRightTabs = (targetPath) => {
+ const index = tabs.value.findIndex((tab) => tab.path === targetPath);
+ if (index === -1 || index >= tabs.value.length - 1) return;
+
+ const toRemove = [];
+ for (let i = index + 1; i < tabs.value.length; i++) {
+ if (tabs.value[i].closable !== false) {
+ toRemove.push(tabs.value[i].path);
+ }
+ }
+
+ tabs.value = tabs.value.filter((tab) => !toRemove.includes(tab.path));
+
+ const currentIdx = tabs.value.findIndex(
+ (tab) => tab.path === activeTab.value,
+ );
+ if (currentIdx === -1) {
+ const next = tabs.value[index + 1] || tabs.value[tabs.value.length - 1];
+ if (next) {
+ activeTab.value = next.path;
+ router.push(next.path);
+ }
+ }
+ };
+
+ const closeOtherTabs = (targetPath) => {
+ tabs.value = tabs.value.filter(
+ (tab) => tab.path === targetPath || tab.closable === false,
+ );
+
+ if (activeTab.value !== targetPath) {
+ const currentTab = tabs.value.find((tab) => tab.path === activeTab.value);
+ if (!currentTab) {
+ activeTab.value = targetPath;
+ router.push(targetPath);
+ }
+ }
+ };
+
+ const closeAllTabs = () => {
+ const unclosable = tabs.value.filter((tab) => tab.closable === false);
+ tabs.value = unclosable.length > 0 ? unclosable : [{ ...HOME_TAB }];
+
+ if (!tabs.value.find((tab) => tab.path === activeTab.value)) {
+ activeTab.value = tabs.value[0].path;
+ router.push(activeTab.value);
+ }
+ };
+
+ const resetTabs = () => {
+ tabs.value = [{ ...HOME_TAB }];
+ activeTab.value = HOME_TAB.path;
+ };
+
+ // 监听标签改变,自动保存到 sessionStorage
+ watch(
+ [tabs, activeTab],
+ ([newTabs, newActiveTab]) => {
+ saveTabsState(newTabs, newActiveTab);
+ },
+ { deep: true },
+ );
+
+ const context = {
+ tabs: computed(() => tabs.value),
+ activeTab: computed(() => activeTab.value),
+ addTab,
+ removeTab,
+ closeLeftTabs,
+ closeRightTabs,
+ closeOtherTabs,
+ closeAllTabs,
+ resetTabs,
+ };
+
+ provide(TABS_CONTEXT_KEY, context);
+
+ return context;
+}
+
+export function useTabsContext() {
+ return inject(TABS_CONTEXT_KEY, null);
+}
diff --git a/src/config/apiRoutes.js b/src/config/apiRoutes.js
index 15dbe5ca62bfc230452faca29f6e556375aae08a..3c2717e102f1e6a05403a89d08f7884d2d1ed13e 100644
--- a/src/config/apiRoutes.js
+++ b/src/config/apiRoutes.js
@@ -12,6 +12,7 @@ export const FLAT_API_ROUTES = Object.freeze({
ADMIN_GET_ALL_ADMIN_TYPES: "/Admin/GetAllAdminTypes",
ADMIN_LOGIN: "/Admin/Login",
LOGIN_LOGOUT: "/Login/Logout",
+ LOGIN_REFRESH_TOKEN: "/Login/RefreshToken",
ADMIN_READ_USER_DIRECT_PERMISSIONS: "/Admin/ReadUserDirectPermissions",
ADMIN_READ_USER_ROLE_PERMISSIONS: "/Admin/ReadUserRolePermissions",
ADMIN_READ_USER_ROLES: "/Admin/ReadUserRoles",
@@ -56,6 +57,7 @@ export const FLAT_API_ROUTES = Object.freeze({
BASE_UPDATE_PASS_PORT_TYPE: "/Base/UpdatePassPortType",
CUSTOMER_DEL_CUSTOMER_INFO: "/Customer/DelCustomerInfo",
CUSTOMER_INSERT_CUSTOMER_INFO: "/Customer/InsertCustomerInfo",
+ CUSTOMER_SELECT_CUSTOMER_BY_INFO: "/Customer/SelectCustoByInfo",
CUSTOMER_PERMISSION_ASSIGN_USER_PERMISSIONS:
"/CustomerPermission/AssignUserPermissions",
CUSTOMER_PERMISSION_ASSIGN_USER_ROLES: "/CustomerPermission/AssignUserRoles",
@@ -76,6 +78,10 @@ export const FLAT_API_ROUTES = Object.freeze({
EMPLOYEE_EMPLOYEE_LOGIN: "/Employee/EmployeeLogin",
EMPLOYEE_HISTORY_SELECT_HISTORY_BY_EMPLOYEE_ID:
"/EmployeeHistory/SelectHistoryByEmployeeId",
+ EMPLOYEE_HISTORY_INSERT_HISTORY_BY_EMPLOYEE_ID:
+ "/EmployeeHistory/AddHistoryByEmployeeId",
+ EMPLOYEE_HISTORY_UPDATE_HISTORY_BY_EMPLOYEE_ID:
+ "/EmployeeHistory/UpdateHistoryByEmployeeId",
EMPLOYEE_MANAGER_EMPLOYEE_ACCOUNT: "/Employee/ManagerEmployeeAccount",
EMPLOYEE_PERMISSION_ASSIGN_USER_PERMISSIONS:
"/EmployeePermission/AssignUserPermissions",
@@ -85,6 +91,13 @@ export const FLAT_API_ROUTES = Object.freeze({
EMPLOYEE_PERMISSION_READ_USER_ROLE_PERMISSIONS:
"/EmployeePermission/ReadUserRolePermissions",
EMPLOYEE_PERMISSION_READ_USER_ROLES: "/EmployeePermission/ReadUserRoles",
+ EMPLOYEE_SELECT_TO_DAY_CHECK_INFO_BY_WORKER_NO:
+ "/EmployeeCheck/SelectToDayCheckInfoByWorkerNo",
+ EMPLOYEE_ADD_CHECK_INFO: "/EmployeeCheck/AddCheckInfo",
+ FAVORITE_COLLECTION_GET_FAVORITE_COLLECTION:
+ "/FavoriteCollection/GetFavoriteCollection",
+ FAVORITE_COLLECTION_SAVE_FAVORITE_COLLECTION:
+ "/FavoriteCollection/SaveFavoriteCollection",
EMPLOYEE_PHOTO_INSERT_WORKER_PHOTO: "/EmployeePhoto/InsertWorkerPhoto",
EMPLOYEE_RESET_EMPLOYEE_ACCOUNT_PASSWORD:
"/Employee/ResetEmployeeAccountPassword",
@@ -94,20 +107,20 @@ export const FLAT_API_ROUTES = Object.freeze({
EMPLOYEE_UPDATE_EMPLOYEE: "/Employee/UpdateEmployee",
ENERGY_MANAGEMENT_DELETE_ENERGY_MANAGEMENT_INFO:
"/EnergyManagement/DeleteEnergyManagementInfo",
- ENERGY_MANAGEMENT_INSERT_ENERGY_MANAGEMENT_INFO:
- "/EnergyManagement/InsertEnergyManagementInfo",
ENERGY_MANAGEMENT_SELECT_ENERGY_MANAGEMENT_INFO:
"/EnergyManagement/SelectEnergyManagementInfo",
ENERGY_MANAGEMENT_UPDATE_ENERGY_MANAGEMENT_INFO:
"/EnergyManagement/UpdateEnergyManagementInfo",
LOGIN_GET_CSRF_TOKEN: "/Login/GetCSRFToken",
- LOGIN_REFRESH_CSRF_TOKEN: "/Login/RefreshCSRFToken",
MENU_BUILD_MENU_ALL: "/Menu/BuildMenuAll",
MENU_DELETE_MENU: "/Menu/DeleteMenu",
MENU_INSERT_MENU: "/Menu/InsertMenu",
MENU_SELECT_MENU_ALL: "/Menu/SelectMenuAll",
MENU_UPDATE_MENU: "/Menu/UpdateMenu",
PERMISSION_SELECT_PERMISSION_LIST: "/Permission/SelectPermissionList",
+ PROFILE_GET_USER_PROFILE: "/Profile/GetUserProfile",
+ PROFILE_CHANGE_PASSWORD: "/Profile/ChangePassword",
+ PROFILE_UPLOAD_AVATAR: "/Profile/UploadAvatar",
PROMOTION_CONTENT_ADD_PROMOTION_CONTENT:
"/PromotionContent/AddPromotionContent",
PROMOTION_CONTENT_DELETE_PROMOTION_CONTENT:
@@ -116,10 +129,12 @@ export const FLAT_API_ROUTES = Object.freeze({
"/PromotionContent/SelectPromotionContentAll",
PROMOTION_CONTENT_UPDATE_PROMOTION_CONTENT:
"/PromotionContent/UpdatePromotionContent",
+ QUARTZ_SELECT_QUARTZ_JOB_LIST: "/Quartz/SelectQuartzJobList",
RESER_DELETE_RESER_INFO: "/Reser/DeleteReserInfo",
RESER_INSER_RESER_INFO: "/Reser/InserReserInfo",
RESER_SELECT_RESER_ALL: "/Reser/SelectReserAll",
RESER_SELECT_RESER_TYPE_ALL: "/Reser/SelectReserTypeAll",
+ RESER_SELECT_RESER_INFO_BY_ROOM_NO: "/Reser/SelectReserInfoByRoomNo",
RESER_UPDATE_RESER_INFO: "/Reser/UpdateReserInfo",
REWARD_PUNISHMENT_SELECT_ALL_REWARD_PUNISHMENT_BY_EMPLOYEE_ID:
"/RewardPunishment/SelectAllRewardPunishmentByEmployeeId",
@@ -127,14 +142,20 @@ export const FLAT_API_ROUTES = Object.freeze({
ROLE_DELETE_ROLE: "/Role/DeleteRole",
ROLE_GRANT_ROLE_PERMISSIONS: "/Role/GrantRolePermissions",
ROLE_INSERT_ROLE: "/Role/InsertRole",
+ ROLE_READ_ROLE_GRANTS: "/Role/ReadRoleGrants",
ROLE_READ_ROLE_PERMISSIONS: "/Role/ReadRolePermissions",
ROLE_READ_ROLE_USERS: "/Role/ReadRoleUsers",
ROLE_SELECT_ROLE_LIST: "/Role/SelectRoleList",
ROLE_UPDATE_ROLE: "/Role/UpdateRole",
ROOM_DELETE_ROOM: "/Room/DeleteRoom",
ROOM_INSERT_ROOM: "/Room/InsertRoom",
+ ROOM_CHECKOUT_ROOM: "/Room/CheckoutRoom",
+ ROOM_TRANSFER_ROOM: "/Room/TransferRoom",
+ ROOM_CHECKIN_ROOM_BY_RESERVATION: "/Room/CheckinRoomByReservation",
ROOM_SELECT_CAN_USE_ROOM_ALL: "/Room/SelectCanUseRoomAll",
ROOM_SELECT_ROOM_ALL: "/Room/SelectRoomAll",
+ ROOM_SELECT_ROOM_PRICING_OPTIONS: "/Room/SelectRoomPricingOptions",
+ ROOM_UPDATE_ROOM_INFO: "/Room/UpdateRoomInfo",
ROOM_TYPE_DELETE_ROOM_TYPE: "/RoomType/DeleteRoomType",
ROOM_TYPE_INSERT_ROOM_TYPE: "/RoomType/InsertRoomType",
ROOM_TYPE_SELECT_ROOM_TYPES_ALL: "/RoomType/SelectRoomTypesAll",
@@ -144,9 +165,10 @@ export const FLAT_API_ROUTES = Object.freeze({
SELLTHING_INSERT_SELLTHING: "/Sellthing/InsertSellthing",
SELLTHING_SELECT_SELLTHING_ALL: "/Sellthing/SelectSellthingAll",
SELLTHING_UPDATE_SELLTHING: "/Sellthing/UpdateSellthing",
- SPEND_INSERT_SPEND_INFO: "/Spend/InsertSpendInfo",
+ SPEND_INSERT_SPEND_INFO: "/Spend/AddCustomerSpend",
SPEND_SELECT_SPEND_INFO_ALL: "/Spend/SelectSpendInfoAll",
SPEND_UPD_SPEND_INFO: "/Spend/UpdSpendInfo",
+ SPEND_UNDO_SPEND_INFO: "/Spend/UndoCustomerSpend",
SUPERVISION_STATISTICS_DELETE_SUPERVISION_STATISTICS:
"/SupervisionStatistics/DeleteSupervisionStatistics",
SUPERVISION_STATISTICS_INSERT_SUPERVISION_STATISTICS:
diff --git a/src/config/dateFields.js b/src/config/dateFields.js
index 682a28e8193117b135bd50da20a6b36a4d00af46..5c33a559b4c4b5a8d4501a5eaa6502555b96049e 100644
--- a/src/config/dateFields.js
+++ b/src/config/dateFields.js
@@ -1,9 +1,20 @@
-import { EmployeeFields } from "@/entities/employee.entity";
-import { LogFields } from "@/entities/log.entity";
-import { SpendInfoFields } from "@/entities/spendinfo.entity";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
+import { LogFields } from "@/entities/operationmanagement/log.entity";
+import { SpendInfoFields } from "@/entities/customermanagement/spendinfo.entity";
+import { RoomFields } from "@/entities/roominformation/room.entity";
+import { SupervisionFields } from "@/entities/supervision/supervision.entity";
export const dateFieldConfig = {
- WITH_TIME: [LogFields.TIME, SpendInfoFields.TIME],
+ WITH_TIME: [
+ LogFields.TIME,
+ SpendInfoFields.TIME,
+ RoomFields.CHECK_IN_TIME,
+ RoomFields.CHECK_OUT_TIME,
+ ],
- WITHOUT_TIME: [EmployeeFields.DATEOFBITRH, EmployeeFields.HIREDATE],
+ WITHOUT_TIME: [
+ EmployeeFields.DATEOFBITRH,
+ EmployeeFields.HIREDATE,
+ SupervisionFields.CHECK_TIME,
+ ],
};
diff --git a/src/entities/department.entity.js b/src/entities/base/department.entity.js
similarity index 71%
rename from src/entities/department.entity.js
rename to src/entities/base/department.entity.js
index 8f4b1bb18543b933af2e7a31128bcfd97c7e6519..4b948d0d0d0bf45ba40ba4cd14d2c57b29d1a0a8 100644
--- a/src/entities/department.entity.js
+++ b/src/entities/base/department.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const DepartmentSpecificFields = {
NUMBER: "DepartmentNumber",
@@ -79,6 +80,28 @@ export const getColumns = (t, handleView) => [
dataIndex: DepartmentFields.CREATIONDATE,
key: DepartmentFields.CREATIONDATE,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
@@ -89,4 +112,11 @@ export const getFormRules = (t) => ({
trigger: "blur",
},
],
+ [DepartmentFields.LEADER]: [
+ {
+ required: true,
+ message: t("message.pleaseInputDepartmentLeader"),
+ trigger: "blur",
+ },
+ ],
});
diff --git a/src/entities/nation.entity.js b/src/entities/base/nation.entity.js
similarity index 58%
rename from src/entities/nation.entity.js
rename to src/entities/base/nation.entity.js
index 072cee8998189b7e2ade5b3126c5c70c3bab1419..b2b84cc6bc43c31f05899c17a0660bafb6c68395 100644
--- a/src/entities/nation.entity.js
+++ b/src/entities/base/nation.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const NationSpecificFields = {
NUMBER: "NationNumber",
@@ -41,6 +42,28 @@ export const getColumns = (t) => [
dataIndex: NationFields.NAME,
key: NationFields.NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/noticetype.entity.js b/src/entities/base/noticetype.entity.js
similarity index 57%
rename from src/entities/noticetype.entity.js
rename to src/entities/base/noticetype.entity.js
index 8ca721ce19d08937f1c96b1e4af4ed4bdb4aaa5b..0992b89bffe1ac907af599626dfe755c07781e8e 100644
--- a/src/entities/noticetype.entity.js
+++ b/src/entities/base/noticetype.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const NoticeTypeSpecificFields = {
NUMBER: "NoticeTypeNumber",
@@ -39,6 +40,28 @@ export const getColumns = (t) => [
dataIndex: NoticeTypeFields.NAME,
key: NoticeTypeFields.NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/passport.entity.js b/src/entities/base/passport.entity.js
similarity index 61%
rename from src/entities/passport.entity.js
rename to src/entities/base/passport.entity.js
index 29e9562437034c1c4e475252d48f49ace509eb03..48910d71c87b0721f7f862ff3300358dde7957a7 100644
--- a/src/entities/passport.entity.js
+++ b/src/entities/base/passport.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const PassportSpecificFields = {
NUMBER: "PassportId",
@@ -44,6 +45,28 @@ export const getColumns = (t) => [
dataIndex: PassportFields.NAME,
key: PassportFields.NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/position.entity.js b/src/entities/base/position.entity.js
similarity index 59%
rename from src/entities/position.entity.js
rename to src/entities/base/position.entity.js
index 1d98564c7e89d176eea799576e0155f740f2efde..a08ebdad819775c23057bfe58fc70b1b174d533e 100644
--- a/src/entities/position.entity.js
+++ b/src/entities/base/position.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const PositionSpecificFields = {
NUMBER: "PositionNumber",
@@ -41,6 +42,28 @@ export const getColumns = (t) => [
dataIndex: PositionFields.NAME,
key: PositionFields.NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/promotioncontent.entity.js b/src/entities/base/promotioncontent.entity.js
similarity index 63%
rename from src/entities/promotioncontent.entity.js
rename to src/entities/base/promotioncontent.entity.js
index 175638ad341bd77df704fac9a7ee99d8e0259c23..59e1edac23e9903930ef173bb7073330df838e53 100644
--- a/src/entities/promotioncontent.entity.js
+++ b/src/entities/base/promotioncontent.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const PromotionContentSpecificFields = {
NUMBER: "PromotionContentNumber",
@@ -28,11 +29,6 @@ export const getColumns = (t) => [
key: "action",
width: 100,
},
- {
- title: t("message.id"),
- dataIndex: PromotionContentFields.ID,
- key: PromotionContentFields.ID,
- },
{
title: t("message.promotionContentNumber"),
dataIndex: PromotionContentFields.NUMBER,
@@ -43,6 +39,28 @@ export const getColumns = (t) => [
dataIndex: PromotionContentFields.MESSAGE,
key: PromotionContentFields.MESSAGE,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/qualification.entity.js b/src/entities/base/qualification.entity.js
similarity index 59%
rename from src/entities/qualification.entity.js
rename to src/entities/base/qualification.entity.js
index 4bcb907d4d67a43b1e6345b2fc92ebce2859a494..2a0b2f630c720497f1720b4db980fb02eb4e5abb 100644
--- a/src/entities/qualification.entity.js
+++ b/src/entities/base/qualification.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const EducationSpecificFields = {
NUMBER: "EducationNumber",
@@ -41,6 +42,28 @@ export const getColumns = (t) => [
dataIndex: EducationFields.NAME,
key: EducationFields.NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/common.entity.js b/src/entities/common.entity.js
index c0fc0e17ba6b7adad1cb6fd7db43f1ec6c2aec14..e99409aaed6f5919e59d10b71fc5b68f44f507d3 100644
--- a/src/entities/common.entity.js
+++ b/src/entities/common.entity.js
@@ -8,6 +8,11 @@ export const BaseFields = {
PAGE_SIZE: "PageSize",
ROWVERSION: "RowVersion",
SELECTED: "selected",
+
+ DATA_INS_USER: "DataInsUsr",
+ DATA_INS_DATE: "DataInsDate",
+ DATA_CHG_USER: "DataChgUsr",
+ DATA_CHG_DATE: "DataChgDate",
};
export const BaseInitialValues = {
diff --git a/src/entities/customer.entity.js b/src/entities/customermanagement/customer.entity.js
similarity index 74%
rename from src/entities/customer.entity.js
rename to src/entities/customermanagement/customer.entity.js
index d417803df98557970a669fe63f7befda7c3e0fd2..f62e36cd0ad0d87ac8c702cc5db7f6d9a98ac214 100644
--- a/src/entities/customer.entity.js
+++ b/src/entities/customermanagement/customer.entity.js
@@ -1,18 +1,19 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const CustomerSpecificFields = {
NUMBER: "CustomerNumber",
- NAME: "CustomerName",
- GENDER: "CustomerGender",
+ NAME: "Name",
+ GENDER: "Gender",
GENDER_NAME: "GenderName",
BIRTH_DATE: "DateOfBirth",
TYPE: "CustomerType",
TYPE_NAME: "CustomerTypeName",
- PASSPORTTYPE: "PassportId",
+ PASSPORTTYPE: "IdCardType",
PASSPORTNAME: "PassportName",
ID_NUMBER: "IdCardNumber",
- PHONE: "CustomerPhoneNumber",
- ADDRESS: "CustomerAddress",
+ PHONE: "PhoneNumber",
+ ADDRESS: "Address",
DATE_RANGE_DTO: "DateRangeDto",
};
@@ -125,6 +126,28 @@ export const getColumns = (t) => [
dataIndex: CustomerFields.ADDRESS,
key: CustomerFields.ADDRESS,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
@@ -164,4 +187,25 @@ export const getFormRules = (t) => ({
trigger: "blur",
},
],
+ [CustomerFields.TYPE]: [
+ {
+ required: true,
+ message: t("message.invalidCustomerType"),
+ trigger: "blur",
+ },
+ ],
+ [CustomerFields.BIRTH_DATE]: [
+ {
+ required: true,
+ message: t("message.invalidCustomerBirth"),
+ trigger: "blur",
+ },
+ ],
+ [CustomerFields.PASSPORTTYPE]: [
+ {
+ required: true,
+ message: t("message.invalidCustomerPassportType"),
+ trigger: "blur",
+ },
+ ],
});
diff --git a/src/entities/customertype.entity.js b/src/entities/customermanagement/customertype.entity.js
similarity index 69%
rename from src/entities/customertype.entity.js
rename to src/entities/customermanagement/customertype.entity.js
index 724844f91f88b5573dcd0702fde7899a774fd3ed..355c41794218cf9d322e0d91b7df4dd37323dda9 100644
--- a/src/entities/customertype.entity.js
+++ b/src/entities/customermanagement/customertype.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const CustomerTypeSpecificFields = {
NUMBER: "CustomerType",
@@ -51,6 +52,28 @@ export const getColumns = (t) => [
dataIndex: CustomerTypeFields.DISCOUNT,
key: CustomerTypeFields.DISCOUNT,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/spendinfo.entity.js b/src/entities/customermanagement/spendinfo.entity.js
similarity index 73%
rename from src/entities/spendinfo.entity.js
rename to src/entities/customermanagement/spendinfo.entity.js
index 86a2629145f254ae635370b3219675531768ace6..5875007b9d16f1c9f478b9d9f7c8e7210db671f3 100644
--- a/src/entities/spendinfo.entity.js
+++ b/src/entities/customermanagement/spendinfo.entity.js
@@ -1,9 +1,14 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
-import { formatDate } from "@/utils/index";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate, formatDateTime } from "@/utils/index";
export const SpendInfoSpecificFields = {
NUMBER: "SpendNumber",
+ PRODUCT_NO: "ProductNumber",
+ ROOM_ID: "RoomId",
ROOM_NO: "RoomNumber",
+ ROOM_LOCATOR: "RoomLocator",
+ ROOM_AREA: "RoomArea",
+ ROOM_FLOOR: "RoomFloor",
CUSTO_NO: "CustomerNumber",
NAME: "ProductName",
AMOUNT: "ConsumptionQuantity",
@@ -14,6 +19,8 @@ export const SpendInfoSpecificFields = {
TIME: "ConsumptionTime",
STATE: "SettlementStatus",
STATE_NAME: "SettlementStatusDescription",
+ TYPE: "ConsumptionType",
+ TYPE_NAME: "ConsumptionTypeDescription",
DATE_RANGE_DTO: "DateRangeDto",
};
@@ -27,6 +34,12 @@ export const SpendState = {
PAID: "Settled",
};
+export const SpendTypes = {
+ ROOM: "room",
+ GOODS: "goods",
+ OTHER: "other",
+};
+
export const StateColors = {
[SpendState.UNPAID]: "#f50",
[SpendState.PAID]: "#87d068",
@@ -37,14 +50,18 @@ export const DATE_FORMAT = "YYYY-MM-DD HH:mm:ss";
export const initialFormValues = {
...BaseInitialValues,
[SpendInfoFields.NUMBER]: null,
+ [SpendInfoFields.ROOM_ID]: null,
[SpendInfoFields.ROOM_NO]: null,
+ [SpendInfoFields.ROOM_LOCATOR]: "",
[SpendInfoFields.CUSTO_NO]: null,
+ [SpendInfoFields.PRODUCT_NO]: null,
[SpendInfoFields.NAME]: "",
[SpendInfoFields.AMOUNT]: 1,
[SpendInfoFields.PRICE]: 0,
[SpendInfoFields.MONEY]: 0,
[SpendInfoFields.TIME]: null,
[SpendInfoFields.STATE]: SpendState.UNPAID,
+ [SpendInfoFields.TYPE]: null,
[SpendInfoFields.DATE_RANGE_DTO]: {
Start: null,
End: null,
@@ -71,12 +88,24 @@ export const getColumns = (t) => [
sorter: (a, b) =>
a[SpendInfoFields.NUMBER].localeCompare(b[SpendInfoFields.NUMBER]),
},
+ {
+ title: t("message.roomLocator"),
+ dataIndex: SpendInfoFields.ROOM_LOCATOR,
+ key: SpendInfoFields.ROOM_LOCATOR,
+ sorter: (a, b) =>
+ String(a[SpendInfoFields.ROOM_LOCATOR] || "").localeCompare(
+ String(b[SpendInfoFields.ROOM_LOCATOR] || ""),
+ "zh-Hans-CN",
+ {
+ numeric: true,
+ },
+ ),
+ },
{
title: t("message.roomNo"),
dataIndex: SpendInfoFields.ROOM_NO,
key: SpendInfoFields.ROOM_NO,
- sorter: (a, b) =>
- a[SpendInfoFields.ROOM_NO].localeCompare(b[SpendInfoFields.ROOM_NO]),
+ hidden: true,
},
{
title: t("message.customerNo"),
@@ -95,6 +124,7 @@ export const getColumns = (t) => [
title: t("message.spendAmount"),
dataIndex: SpendInfoFields.AMOUNT,
key: SpendInfoFields.AMOUNT,
+ width: 80,
},
{
title: t("message.spendPrice"),
@@ -112,11 +142,13 @@ export const getColumns = (t) => [
title: t("message.spendPrice"),
dataIndex: SpendInfoFields.PRICEFORMATTED,
key: SpendInfoFields.PRICEFORMATTED,
+ width: 120,
},
{
title: t("message.spendMoney"),
dataIndex: SpendInfoFields.MONEYFORMATTED,
key: SpendInfoFields.MONEYFORMATTED,
+ width: 120,
},
{
title: t("message.spendTime"),
@@ -131,6 +163,28 @@ export const getColumns = (t) => [
key: SpendInfoFields.STATE,
width: 100,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/viprule.entity.js b/src/entities/customermanagement/viprule.entity.js
similarity index 72%
rename from src/entities/viprule.entity.js
rename to src/entities/customermanagement/viprule.entity.js
index 2524ffbc3c532016e715c652a66579f5f45ee75b..b206d52b50beccf780f2d89b58cda79b6282a644 100644
--- a/src/entities/viprule.entity.js
+++ b/src/entities/customermanagement/viprule.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const VipRuleSpecificFields = {
NUMBER: "RuleSerialNumber",
@@ -59,6 +60,28 @@ export const getColumns = (t) => [
dataIndex: VipRuleFields.CUSTOMER_TYPE_NAME,
key: VipRuleFields.CUSTOMER_TYPE_NAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/employeehistory.entity.js b/src/entities/employeehistory.entity.js
deleted file mode 100644
index 76bfb9352de501c65f9bfb5e4a248ba62d16caf5..0000000000000000000000000000000000000000
--- a/src/entities/employeehistory.entity.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
-import { formatDate } from "@/utils/index";
-
-export const EmployeeHistorySpecificFields = {
- NUMBER: "HistoryNumber",
- EMPLOYEENUMBER: "EmployeeId",
- STARTDATE: "StartDate",
- ENDDATE: "EndDate",
- POSITION: "Position",
- COMPANY: "Company",
-};
-
-export const DATE_FORMATS = {
- SHORT_DATE: "YYYY-MM-DD",
- LONG_DATE: "MMMM Do YYYY",
- TABLE_DATE: "MM/DD/YYYY",
-};
-
-export const EmployeeHistoryFields = {
- ...EmployeeHistorySpecificFields,
- ...BaseFields,
-};
-
-export const initialFormValues = {
- ...BaseInitialValues,
- [EmployeeHistoryFields.NUMBER]: null,
- [EmployeeHistoryFields.EMPLOYEENUMBER]: "",
- [EmployeeHistoryFields.STARTDATE]: null,
- [EmployeeHistoryFields.ENDDATE]: null,
- [EmployeeHistoryFields.POSITION]: "",
- [EmployeeHistoryFields.COMPANY]: "",
-};
-
-export const getHistoryColumns = (t) => [
- {
- title: t("message.hireDate"),
- dataIndex: EmployeeHistoryFields.STARTDATE,
- key: EmployeeHistoryFields.STARTDATE,
- customRender: ({ text }) => formatDate(text, DATE_FORMATS.TABLE_DATE),
- },
- {
- title: t("message.departureDate"),
- dataIndex: EmployeeHistoryFields.ENDDATE,
- key: EmployeeHistoryFields.ENDDATE,
- customRender: ({ text }) => formatDate(text, DATE_FORMATS.TABLE_DATE),
- },
- {
- title: t("message.historyPosition"),
- dataIndex: EmployeeHistoryFields.POSITION,
- key: EmployeeHistoryFields.POSITION,
- },
- {
- title: t("message.company"),
- dataIndex: EmployeeHistoryFields.COMPANY,
- key: EmployeeHistoryFields.COMPANY,
- },
-];
-
-export const getFormRules = (t) => ({});
diff --git a/src/entities/internalfinance.entity.js b/src/entities/finance/internalfinance.entity.js
similarity index 81%
rename from src/entities/internalfinance.entity.js
rename to src/entities/finance/internalfinance.entity.js
index 9c66ee7eed36d2c9f61f47a6450413e124086c76..0a53c52f58a0cad09e30691d054aca46b0f1cc4f 100644
--- a/src/entities/internalfinance.entity.js
+++ b/src/entities/finance/internalfinance.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const InternalFinanceSpecificFields = {
NUMBER: "AssetNumber",
@@ -10,7 +11,8 @@ export const InternalFinanceSpecificFields = {
ACQUISITIONDATE: "AcquisitionDate",
ASSETSOURCE: "AssetSource",
ACQUIREDBYEMPLOYEE: "AcquiredByEmployeeId",
- ACQUIREDBYEMPLOYEENAME: "AcquiredByEmployeeName",
+ ACQUIREDBYEMPLOYEENAME: "AcquiredByName",
+ DATE_RANGE_DTO: "DateRangeDto",
};
export const InternalFinanceFields = {
@@ -30,6 +32,11 @@ export const initialFormValues = {
[InternalFinanceFields.ASSETSOURCE]: "",
[InternalFinanceFields.ACQUIREDBYEMPLOYEE]: "",
[InternalFinanceFields.ACQUIREDBYEMPLOYEENAME]: "",
+ [InternalFinanceFields.DATE_RANGE_DTO]: {
+ Start: null,
+ End: null,
+ Ranges: null,
+ },
};
export const getColumns = (t) => [
@@ -102,6 +109,28 @@ export const getColumns = (t) => [
dataIndex: InternalFinanceFields.ACQUIREDBYEMPLOYEENAME,
key: InternalFinanceFields.ACQUIREDBYEMPLOYEENAME,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/employee.entity.js b/src/entities/humanresourcemanagement/employee.entity.js
similarity index 90%
rename from src/entities/employee.entity.js
rename to src/entities/humanresourcemanagement/employee.entity.js
index 0d6099ea38642940a31b674ec951e4a4bd98e460..3810e367d6f6426f0c9bb43076b0bdd19517c1dc 100644
--- a/src/entities/employee.entity.js
+++ b/src/entities/humanresourcemanagement/employee.entity.js
@@ -1,9 +1,9 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
-import { formatDate } from "@/utils/index";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate, formatDateTime } from "@/utils/index";
export const EmployeeSpecificFields = {
NUMBER: "EmployeeId",
- NAME: "EmployeeName",
+ NAME: "Name",
GENDER: "Gender",
GENDERNAME: "GenderName",
DATEOFBIRTH: "DateOfBirth",
@@ -187,6 +187,28 @@ export const getColumns = (t) => [
dataIndex: EmployeeFields.ISENABLE,
key: EmployeeFields.ISENABLE,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/employeecheck.entity.js b/src/entities/humanresourcemanagement/employeecheck.entity.js
similarity index 79%
rename from src/entities/employeecheck.entity.js
rename to src/entities/humanresourcemanagement/employeecheck.entity.js
index a6fd865e09959f843bfbdc0ac59fa0f021a5ba32..50a7dd33dc03e747ae4b17b4f66e41566c72cfe8 100644
--- a/src/entities/employeecheck.entity.js
+++ b/src/entities/humanresourcemanagement/employeecheck.entity.js
@@ -1,4 +1,4 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
import { formatDate } from "@/utils/index";
export const EmployeeCheckSpecificFields = {
@@ -7,6 +7,8 @@ export const EmployeeCheckSpecificFields = {
CHECKTIME: "CheckTime",
CHECKSTATUS: "CheckStatus",
CHECKMETHOD: "CheckMethod",
+ CHECKSTATUSDESC: "CheckStatusDescription",
+ CHECKMETHODDESC: "CheckMethodDescription",
};
export const DATE_FORMATS = {
@@ -45,6 +47,12 @@ export const getCheckColumns = (t) => [
title: t("message.checkMethod"),
dataIndex: EmployeeCheckFields.CHECKMETHOD,
key: EmployeeCheckFields.CHECKMETHOD,
+ hidden: true,
+ },
+ {
+ title: t("message.checkMethod"),
+ dataIndex: EmployeeCheckFields.CHECKMETHODDESC,
+ key: EmployeeCheckFields.CHECKMETHODDESC,
},
];
diff --git a/src/entities/humanresourcemanagement/employeehistory.entity.js b/src/entities/humanresourcemanagement/employeehistory.entity.js
new file mode 100644
index 0000000000000000000000000000000000000000..7e2db5da5c9562545429c3db2dd5d643c078253b
--- /dev/null
+++ b/src/entities/humanresourcemanagement/employeehistory.entity.js
@@ -0,0 +1,112 @@
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate } from "@/utils/index";
+
+export const EmployeeHistorySpecificFields = {
+ NUMBER: "HistoryNumber",
+ EMPLOYEENUMBER: "EmployeeId",
+ STARTDATE: "StartDate",
+ ENDDATE: "EndDate",
+ POSITION: "Position",
+ COMPANY: "Company",
+};
+
+export const DATE_FORMATS = {
+ SHORT_DATE: "YYYY-MM-DD",
+ LONG_DATE: "MMMM Do YYYY",
+ TABLE_DATE: "MM/DD/YYYY",
+};
+
+export const EmployeeHistoryFields = {
+ ...EmployeeHistorySpecificFields,
+ ...BaseFields,
+};
+
+export const EmployeeHistoryColumnKeys = {
+ ACTION: "action",
+};
+
+export const initialFormValues = {
+ ...BaseInitialValues,
+ [EmployeeHistoryFields.NUMBER]: "",
+ [EmployeeHistoryFields.EMPLOYEENUMBER]: "",
+ [EmployeeHistoryFields.STARTDATE]: null,
+ [EmployeeHistoryFields.ENDDATE]: null,
+ [EmployeeHistoryFields.POSITION]: "",
+ [EmployeeHistoryFields.COMPANY]: "",
+};
+
+export const getHistoryColumns = (t, { includeActionColumn = false } = {}) => {
+ const columns = [
+ {
+ title: t("message.hireDate"),
+ dataIndex: EmployeeHistoryFields.STARTDATE,
+ key: EmployeeHistoryFields.STARTDATE,
+ customRender: ({ text }) => formatDate(text, DATE_FORMATS.TABLE_DATE),
+ },
+ {
+ title: t("message.departureDate"),
+ dataIndex: EmployeeHistoryFields.ENDDATE,
+ key: EmployeeHistoryFields.ENDDATE,
+ customRender: ({ text }) => formatDate(text, DATE_FORMATS.TABLE_DATE),
+ },
+ {
+ title: t("message.historyPosition"),
+ dataIndex: EmployeeHistoryFields.POSITION,
+ key: EmployeeHistoryFields.POSITION,
+ },
+ {
+ title: t("message.company"),
+ dataIndex: EmployeeHistoryFields.COMPANY,
+ key: EmployeeHistoryFields.COMPANY,
+ },
+ ];
+
+ if (!includeActionColumn) {
+ return columns;
+ }
+
+ return [
+ {
+ title: t("message.action"),
+ key: EmployeeHistoryColumnKeys.ACTION,
+ width: 80,
+ align: "center",
+ },
+ ...columns,
+ ];
+};
+
+export const getFormRules = (t) => ({
+ [EmployeeHistoryFields.STARTDATE]: [
+ {
+ required: true,
+ message: t("message.pleaseSelectDate"),
+ trigger: "change",
+ },
+ ],
+ [EmployeeHistoryFields.ENDDATE]: [
+ {
+ required: true,
+ message: t("message.pleaseSelectDate"),
+ trigger: "change",
+ },
+ ],
+ [EmployeeHistoryFields.POSITION]: [
+ {
+ required: true,
+ message: t("message.required", {
+ field: t("message.historyPosition"),
+ }),
+ trigger: "blur",
+ },
+ ],
+ [EmployeeHistoryFields.COMPANY]: [
+ {
+ required: true,
+ message: t("message.required", {
+ field: t("message.company"),
+ }),
+ trigger: "blur",
+ },
+ ],
+});
diff --git a/src/entities/rewardpunishment.entity.js b/src/entities/humanresourcemanagement/rewardpunishment.entity.js
similarity index 96%
rename from src/entities/rewardpunishment.entity.js
rename to src/entities/humanresourcemanagement/rewardpunishment.entity.js
index da8d9acf21b396639603c81fabb38e92dba266e2..0c43e2779717e6f229f2c0a04f191e72a31a50b3 100644
--- a/src/entities/rewardpunishment.entity.js
+++ b/src/entities/humanresourcemanagement/rewardpunishment.entity.js
@@ -1,4 +1,4 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
import { formatDate } from "@/utils/index";
export const EmployeeRewardPunishmentSpecificFields = {
diff --git a/src/entities/energymanagement.entity.js b/src/entities/hydroelectricity/energymanagement.entity.js
similarity index 70%
rename from src/entities/energymanagement.entity.js
rename to src/entities/hydroelectricity/energymanagement.entity.js
index 4fa867ed172c659ef10d877cb4425660146c7d82..37a9f5d6909d59430a513615f09eb4824bf1139d 100644
--- a/src/entities/energymanagement.entity.js
+++ b/src/entities/hydroelectricity/energymanagement.entity.js
@@ -1,9 +1,13 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
-import { formatDate } from "@/utils/index";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate, formatDateTime } from "@/utils/index";
export const EnergyManagementSpecificFields = {
NUMBER: "InformationId",
+ ROOM_ID: "RoomId",
ROOMNUMBER: "RoomNumber",
+ ROOMLOCATOR: "RoomLocator",
+ ROOMAREA: "RoomArea",
+ ROOMFLOOR: "RoomFloor",
CUSTOMERNUMBER: "CustomerNumber",
STARTDATE: "StartDate",
ENDDATE: "EndDate",
@@ -20,7 +24,9 @@ export const EnergyManagementFields = {
export const initialFormValues = {
...BaseInitialValues,
[EnergyManagementFields.NUMBER]: "",
+ [EnergyManagementFields.ROOM_ID]: null,
[EnergyManagementFields.ROOMNUMBER]: "",
+ [EnergyManagementFields.ROOMLOCATOR]: "",
[EnergyManagementFields.CUSTOMERNUMBER]: "",
[EnergyManagementFields.STARTDATE]: null,
[EnergyManagementFields.ENDDATE]: null,
@@ -46,10 +52,16 @@ export const getColumns = (t) => [
dataIndex: [EnergyManagementFields.NUMBER],
key: [EnergyManagementFields.NUMBER],
},
+ {
+ title: t("message.roomLocator"),
+ dataIndex: [EnergyManagementFields.ROOMLOCATOR],
+ key: [EnergyManagementFields.ROOMLOCATOR],
+ },
{
title: t("message.roomNo"),
dataIndex: [EnergyManagementFields.ROOMNUMBER],
key: [EnergyManagementFields.ROOMNUMBER],
+ hidden: true,
},
{
title: t("message.custoNo"),
@@ -85,6 +97,28 @@ export const getColumns = (t) => [
dataIndex: [EnergyManagementFields.RECORDER],
key: [EnergyManagementFields.RECORDER],
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/goods.entity.js b/src/entities/materialmanagement/goods.entity.js
similarity index 73%
rename from src/entities/goods.entity.js
rename to src/entities/materialmanagement/goods.entity.js
index 20a2d61846eab5a0d75713a15abd74fdc815fa2c..af1fdb50bb264ccf9e8e972b5c7f85b0ae2ecce8 100644
--- a/src/entities/goods.entity.js
+++ b/src/entities/materialmanagement/goods.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const GoodsSpecificFields = {
NUMBER: "ProductNumber",
@@ -63,6 +64,28 @@ export const getColumns = (t) => [
dataIndex: GoodsFields.STOCK,
key: GoodsFields.STOCK,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/log.entity.js b/src/entities/operationmanagement/log.entity.js
similarity index 82%
rename from src/entities/log.entity.js
rename to src/entities/operationmanagement/log.entity.js
index a9e832b9fbb8c89ed76e39ace5cd3836c3b92963..989fc1a4a0576d63b02f44b013926f7a662fab19 100644
--- a/src/entities/log.entity.js
+++ b/src/entities/operationmanagement/log.entity.js
@@ -1,5 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
-import { formatDate } from "@/utils/index";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate, formatDateTime } from "@/utils/index";
export const LogSpecificFields = {
NUMBER: "OperationId",
@@ -87,6 +87,17 @@ export const getColumns = (t) => [
dataIndex: LogFields.IPADDRESS,
key: LogFields.IPADDRESS,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/requestlog.entity.js b/src/entities/operationmanagement/requestlog.entity.js
similarity index 81%
rename from src/entities/requestlog.entity.js
rename to src/entities/operationmanagement/requestlog.entity.js
index c0d1d6e7152150b3fb4616dcac749ae117f2577d..9885ddc627002d9ca87ad1825ab3a344ad2eed01 100644
--- a/src/entities/requestlog.entity.js
+++ b/src/entities/operationmanagement/requestlog.entity.js
@@ -1,4 +1,4 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
import { formatDateTime } from "@/utils/index";
export const LogSpecificFields = {
@@ -77,14 +77,14 @@ export const getColumns = (t) => [
title: t("message.clientIP"),
dataIndex: LogFields.CLIENTIP,
key: LogFields.CLIENTIP,
- width: 300,
+ width: 120,
ellipsis: true,
},
{
title: t("message.parameters"),
dataIndex: LogFields.PARAMETERS,
key: LogFields.PARAMETERS,
- width: 300,
+ width: 150,
ellipsis: true,
},
{
@@ -98,7 +98,7 @@ export const getColumns = (t) => [
title: t("message.elapsedMilliseconds"),
dataIndex: LogFields.ELAPSEDMILLISECONDS,
key: LogFields.ELAPSEDMILLISECONDS,
- width: 100,
+ width: 90,
ellipsis: true,
},
{
@@ -143,6 +143,28 @@ export const getColumns = (t) => [
width: 180,
ellipsis: true,
},
+ // {
+ // title: t("message.createdBy"),
+ // dataIndex: BaseFields.DATA_INS_USER,
+ // key: BaseFields.DATA_INS_USER,
+ // },
+ // {
+ // title: t("message.createdTime"),
+ // dataIndex: BaseFields.DATA_INS_DATE,
+ // key: BaseFields.DATA_INS_DATE,
+ // customRender: ({ text }) => formatDateTime(text),
+ // },
+ // {
+ // title: t("message.lastUpdatedBy"),
+ // dataIndex: BaseFields.DATA_CHG_USER,
+ // key: BaseFields.DATA_CHG_USER,
+ // },
+ // {
+ // title: t("message.lastUpdatedTime"),
+ // dataIndex: BaseFields.DATA_CHG_DATE,
+ // key: BaseFields.DATA_CHG_DATE,
+ // customRender: ({ text }) => formatDateTime(text),
+ // },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/reser.entity.js b/src/entities/roominformation/reser.entity.js
similarity index 72%
rename from src/entities/reser.entity.js
rename to src/entities/roominformation/reser.entity.js
index 2f78aa5780987ba385dcce5b510e3c781eea0b01..20dedfe68bb9e959d23673152a4b66dc9bd17e17 100644
--- a/src/entities/reser.entity.js
+++ b/src/entities/roominformation/reser.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const ReserSpecificFields = {
NUMBER: "ReservationId",
@@ -6,7 +7,11 @@ export const ReserSpecificFields = {
PHONENUMBER: "ReservationPhoneNumber",
CHANNEL: "ReservationChannel",
CHANNELDESCRIPTION: "ReservationChannelDescription",
+ ROOM_ID: "RoomId",
ROOMNUMBER: "ReservationRoomNumber",
+ ROOMAREA: "RoomArea",
+ ROOMFLOOR: "RoomFloor",
+ ROOMLOCATOR: "RoomLocator",
STARTDATE: "ReservationStartDate",
ENDDATE: "ReservationEndDate",
};
@@ -16,13 +21,23 @@ export const ReserFields = {
...BaseFields,
};
+export const ReserChannelValues = {
+ OFFLINE: "Offline",
+ APP: "App",
+ APPLET: "Applet",
+ WEBSITE: "Website",
+ OTHER: "Other",
+};
+
export const initialFormValues = {
...BaseInitialValues,
[ReserFields.NUMBER]: null,
[ReserFields.CUSTOMERNAME]: "",
[ReserFields.PHONENUMBER]: "",
[ReserFields.CHANNEL]: "",
+ [ReserFields.ROOM_ID]: null,
[ReserFields.ROOMNUMBER]: "",
+ [ReserFields.ROOMLOCATOR]: "",
[ReserFields.STARTDATE]: null,
[ReserFields.ENDDATE]: null,
};
@@ -68,10 +83,16 @@ export const getColumns = (t) => [
dataIndex: ReserFields.CHANNELDESCRIPTION,
key: ReserFields.CHANNELDESCRIPTION,
},
+ {
+ title: t("message.roomLocator"),
+ dataIndex: ReserFields.ROOMLOCATOR,
+ key: ReserFields.ROOMLOCATOR,
+ },
{
title: t("message.reserRoomNumber"),
dataIndex: ReserFields.ROOMNUMBER,
key: ReserFields.ROOMNUMBER,
+ hidden: true,
},
{
title: t("message.reserStartDate"),
@@ -83,6 +104,28 @@ export const getColumns = (t) => [
dataIndex: ReserFields.ENDDATE,
key: ReserFields.ENDDATE,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
@@ -122,13 +165,13 @@ export const getFormRules = (t) => ({
trigger: "blur",
},
],
- [ReserFields.ROOMNUMBER]: [
+ [ReserFields.ROOM_ID]: [
{
required: true,
message: t("message.required", {
- field: t("message.reserRoomNumber"),
+ field: t("message.roomLocator"),
}),
- trigger: "blur",
+ trigger: "change",
},
],
[ReserFields.STARTDATE]: [
diff --git a/src/entities/room.entity.js b/src/entities/roominformation/room.entity.js
similarity index 34%
rename from src/entities/room.entity.js
rename to src/entities/roominformation/room.entity.js
index 34054c35b12791546016536d1a18556b0c809385..4084d40228367921c12db433f67aef951bd7bf70 100644
--- a/src/entities/room.entity.js
+++ b/src/entities/roominformation/room.entity.js
@@ -1,15 +1,27 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const RoomSpecificFields = {
NO: "RoomNumber",
+ AREA: "RoomArea",
+ FLOOR: "RoomFloor",
+ LOCATOR: "RoomLocator",
+ LOCATION: "RoomLocation",
NAME: "RoomName",
STATE: "RoomState",
- POSITION: "RoomLocation",
CUSTOMER_NAME: "CustomerName",
CHECK_IN_TIME: "LastCheckInTime",
CHECK_OUT_TIME: "LastCheckOutTime",
RENT: "RoomRent",
DEPOSIT: "RoomDeposit",
+ STANDARD_RENT: "StandardRoomRent",
+ STANDARD_DEPOSIT: "StandardRoomDeposit",
+ APPLIED_RENT: "AppliedRoomRent",
+ APPLIED_DEPOSIT: "AppliedRoomDeposit",
+ EFFECTIVE_RENT: "EffectiveRoomRent",
+ EFFECTIVE_DEPOSIT: "EffectiveRoomDeposit",
+ PRICING_CODE: "PricingCode",
+ PRICING_NAME: "PricingName",
STATE_ID: "RoomStateId",
TYPE: "RoomTypeId",
};
@@ -19,6 +31,60 @@ export const RoomFields = {
...BaseFields,
};
+export const RoomOperationSpecificFields = {
+ ROOM_ID: "RoomId",
+ ORIGINAL_ROOM_ID: "OriginalRoomId",
+ TARGET_ROOM_ID: "TargetRoomId",
+ ORIGINAL_ROOM_NUMBER: "OriginalRoomNumber",
+ TARGET_ROOM_NUMBER: "TargetRoomNumber",
+ WATER_USAGE: "WaterUsage",
+ ELECTRICITY_USAGE: "ElectricityUsage",
+};
+
+export const RoomOperationFields = {
+ ...RoomOperationSpecificFields,
+};
+
+export const getRoomEffectiveRent = (room = {}) => {
+ const effectiveRent = Number(room?.[RoomFields.EFFECTIVE_RENT]);
+ if (Number.isFinite(effectiveRent)) {
+ return effectiveRent;
+ }
+
+ const roomRent = Number(room?.[RoomFields.RENT]);
+ return Number.isFinite(roomRent) ? roomRent : 0;
+};
+
+export const getRoomEffectiveDeposit = (room = {}) => {
+ const effectiveDeposit = Number(room?.[RoomFields.EFFECTIVE_DEPOSIT]);
+ if (Number.isFinite(effectiveDeposit)) {
+ return effectiveDeposit;
+ }
+
+ const roomDeposit = Number(room?.[RoomFields.DEPOSIT]);
+ return Number.isFinite(roomDeposit) ? roomDeposit : 0;
+};
+
+export const getRoomStandardRent = (room = {}) => {
+ const standardRent = Number(room?.[RoomFields.STANDARD_RENT]);
+ if (Number.isFinite(standardRent)) {
+ return standardRent;
+ }
+
+ const roomRent = Number(room?.[RoomFields.RENT]);
+ return Number.isFinite(roomRent) ? roomRent : 0;
+};
+
+export const getRoomStandardDeposit = (room = {}) => {
+ const standardDeposit = Number(room?.[RoomFields.STANDARD_DEPOSIT]);
+ if (Number.isFinite(standardDeposit)) {
+ return standardDeposit;
+ }
+
+ const roomDeposit = Number(room?.[RoomFields.DEPOSIT]);
+ return Number.isFinite(roomDeposit) ? roomDeposit : 0;
+};
+
export const RoomStateMap = {
vacant: "空房",
occupied: "已住",
@@ -27,6 +93,14 @@ export const RoomStateMap = {
reserved: "预约",
};
+export const RoomStateValues = {
+ VACANT: "空房",
+ OCCUPIED: "已住",
+ MAINTENANCE: "维修",
+ DIRTY: "脏房",
+ RESERVED: "预约",
+};
+
export const RoomStateColors = {
空房: "#48a54b",
已住: "#1f8de5",
@@ -38,11 +112,14 @@ export const RoomStateColors = {
export const initialFormValues = {
...BaseInitialValues,
[RoomFields.NO]: null,
+ [RoomFields.AREA]: "",
+ [RoomFields.FLOOR]: null,
+ [RoomFields.LOCATOR]: "",
+ [RoomFields.LOCATION]: "",
[RoomFields.NAME]: "",
[RoomFields.STATE]: "vacant",
[RoomFields.STATE_ID]: 0,
[RoomFields.TYPE]: null,
- [RoomFields.POSITION]: "",
[RoomFields.CUSTOMER_NAME]: "",
[RoomFields.CHECK_IN_TIME]: null,
[RoomFields.CHECK_OUT_TIME]: null,
@@ -50,6 +127,21 @@ export const initialFormValues = {
[RoomFields.DEPOSIT]: 0,
};
+export const getRoomLocatorText = (room = {}) => {
+ const locator = String(room?.[RoomFields.LOCATOR] || "").trim();
+ if (locator) {
+ return locator;
+ }
+
+ const area = String(room?.[RoomFields.AREA] || "").trim();
+ const floor = room?.[RoomFields.FLOOR];
+ const roomNo = String(room?.[RoomFields.NO] || "").trim();
+ const floorText =
+ floor === null || floor === undefined || floor === "" ? "" : `${floor}F`;
+
+ return [area, floorText, roomNo].filter(Boolean).join(" / ");
+};
+
export const getColumns = (t) => [
{
title: "",
@@ -63,10 +155,13 @@ export const getColumns = (t) => [
width: 100,
},
{
- title: t("message.roomNo"),
- dataIndex: RoomFields.NO,
- key: RoomFields.NO,
- sorter: (a, b) => a[RoomFields.NO].localeCompare(b[RoomFields.NO]),
+ title: t("message.roomLocator"),
+ dataIndex: RoomFields.LOCATOR,
+ key: RoomFields.LOCATOR,
+ sorter: (a, b) =>
+ getRoomLocatorText(a).localeCompare(getRoomLocatorText(b), "zh-Hans-CN", {
+ numeric: true,
+ }),
defaultSortOrder: "ascend",
},
{
@@ -75,6 +170,21 @@ export const getColumns = (t) => [
key: RoomFields.TYPE,
hidden: true,
},
+ {
+ title: t("message.roomArea"),
+ dataIndex: RoomFields.AREA,
+ key: RoomFields.AREA,
+ },
+ {
+ title: t("message.roomFloor"),
+ dataIndex: RoomFields.FLOOR,
+ key: RoomFields.FLOOR,
+ },
+ {
+ title: t("message.roomNo"),
+ dataIndex: RoomFields.NO,
+ key: RoomFields.NO,
+ },
{
title: t("message.roomType"),
dataIndex: RoomFields.NAME,
@@ -92,19 +202,46 @@ export const getColumns = (t) => [
key: "RoomState",
},
{
- title: t("message.roomRent"),
+ title: t("message.currentPricing"),
+ dataIndex: RoomFields.PRICING_NAME,
+ key: RoomFields.PRICING_NAME,
+ },
+ {
+ title: t("message.currentRoomRent"),
dataIndex: RoomFields.RENT,
key: RoomFields.RENT,
},
{
- title: t("message.roomDeposit"),
+ title: t("message.currentRoomDeposit"),
dataIndex: RoomFields.DEPOSIT,
key: RoomFields.DEPOSIT,
},
{
- title: t("message.roomPosition"),
- dataIndex: RoomFields.POSITION,
- key: RoomFields.POSITION,
+ title: t("message.roomLocation"),
+ dataIndex: RoomFields.LOCATION,
+ key: RoomFields.LOCATION,
+ },
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
},
];
diff --git a/src/entities/roomconfig.entity.js b/src/entities/roominformation/roomconfig.entity.js
similarity index 57%
rename from src/entities/roomconfig.entity.js
rename to src/entities/roominformation/roomconfig.entity.js
index 67e31a88500027100f5face1b140f47157d8723a..7184672f3bb4c33dc1a4f635915591de424c0396 100644
--- a/src/entities/roomconfig.entity.js
+++ b/src/entities/roominformation/roomconfig.entity.js
@@ -1,23 +1,49 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const RoomConfigSpecificFields = {
NO: "RoomTypeId",
NAME: "RoomTypeName",
RENT: "RoomRent",
DEPOSIT: "RoomDeposit",
+ PRICING_ITEMS: "PricingItems",
};
+export const RoomConfigPricingItemFields = {
+ CODE: "PricingCode",
+ NAME: "PricingName",
+ RENT: "RoomRent",
+ DEPOSIT: "RoomDeposit",
+ STAY_HOURS: "StayHours",
+ SORT: "Sort",
+ IS_DEFAULT: "IsDefault",
+};
+
+export const STANDARD_PRICING_CODE = "STANDARD";
+
export const RoomConfigFields = {
...RoomConfigSpecificFields,
...BaseFields,
};
+export const createRoomConfigPricingItem = (overrides = {}) => ({
+ [RoomConfigPricingItemFields.CODE]: "",
+ [RoomConfigPricingItemFields.NAME]: "",
+ [RoomConfigPricingItemFields.RENT]: 0,
+ [RoomConfigPricingItemFields.DEPOSIT]: 0,
+ [RoomConfigPricingItemFields.STAY_HOURS]: null,
+ [RoomConfigPricingItemFields.SORT]: 0,
+ [RoomConfigPricingItemFields.IS_DEFAULT]: false,
+ ...overrides,
+});
+
export const initialFormValues = {
...BaseInitialValues,
[RoomConfigFields.NO]: null,
[RoomConfigFields.NAME]: "",
[RoomConfigFields.RENT]: 0,
[RoomConfigFields.DEPOSIT]: 0,
+ [RoomConfigFields.PRICING_ITEMS]: [],
};
export const getColumns = (t) => [
@@ -55,6 +81,28 @@ export const getColumns = (t) => [
dataIndex: RoomConfigFields.DEPOSIT,
key: RoomConfigFields.DEPOSIT,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/supervision.entity.js b/src/entities/supervision/supervision.entity.js
similarity index 72%
rename from src/entities/supervision.entity.js
rename to src/entities/supervision/supervision.entity.js
index 97e3bc65ff3d54546bb1650389ca4549dfbab7c6..e1456bcc330a6040971ff9db9ae3c5853a5e7f08 100644
--- a/src/entities/supervision.entity.js
+++ b/src/entities/supervision/supervision.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDate, formatDateTime } from "@/utils/index";
export const SupervisionSpecificFields = {
CHECK_NO: "StatisticsNumber",
@@ -9,6 +10,8 @@ export const SupervisionSpecificFields = {
CHECK_SCORE: "SupervisionScore",
CHECK_PERSON: "SupervisionStatistician",
CHECK_ADVICE: "SupervisionAdvice",
+ CHECK_TIME: "SupervisionTime",
+ DATE_RANGE_DTO: "DateRangeDto",
};
export const SupervisionFields = {
@@ -25,6 +28,12 @@ export const initialFormValues = {
[SupervisionFields.CHECK_SCORE]: 0,
[SupervisionFields.CHECK_PERSON]: "",
[SupervisionFields.CHECK_ADVICE]: "",
+ [SupervisionFields.DATE_RANGE_DTO]: {
+ Start: null,
+ End: null,
+ Ranges: null,
+ },
+ [SupervisionFields.CHECK_TIME]: null,
};
export const getColumns = (t) => [
@@ -49,6 +58,11 @@ export const getColumns = (t) => [
),
defaultSortOrder: "ascend",
},
+ {
+ title: t("message.checkDate"),
+ dataIndex: SupervisionFields.CHECK_TIME,
+ key: SupervisionFields.CHECK_TIME,
+ },
{
title: t("message.checkDepartment"),
dataIndex: SupervisionFields.CHECK_CLUB,
@@ -85,6 +99,28 @@ export const getColumns = (t) => [
dataIndex: SupervisionFields.CHECK_ADVICE,
key: SupervisionFields.CHECK_ADVICE,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
@@ -130,4 +166,11 @@ export const getFormRules = (t) => ({
trigger: "blur",
},
],
+ [SupervisionFields.CHECK_TIME]: [
+ {
+ required: true,
+ message: t("message.pleaseInputCheckDate"),
+ trigger: "blur",
+ },
+ ],
});
diff --git a/src/entities/administrator.entity.js b/src/entities/systemmanagement/administrator.entity.js
similarity index 83%
rename from src/entities/administrator.entity.js
rename to src/entities/systemmanagement/administrator.entity.js
index eceaf8e6b019c8f096234c3a3677c217fe9d7ae3..b339af5d5283e41a3e82fc025d570162dbb5828a 100644
--- a/src/entities/administrator.entity.js
+++ b/src/entities/systemmanagement/administrator.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const AdministratorSpecificFields = {
NUMBER: "Number",
@@ -93,6 +94,28 @@ export const getColumns = (t) => [
dataIndex: AdministratorFields.ISSUPERADMINDESCRIPTION,
key: AdministratorFields.ISSUPERADMINDESCRIPTION,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t, isEditMode = false) => ({
diff --git a/src/entities/administratortype.entity.js b/src/entities/systemmanagement/administratortype.entity.js
similarity index 68%
rename from src/entities/administratortype.entity.js
rename to src/entities/systemmanagement/administratortype.entity.js
index 634e342a968843869fc935f6ef4ac24eaa4031b6..2c52c704ea99df291d58a767a7bbc4167efa7e75 100644
--- a/src/entities/administratortype.entity.js
+++ b/src/entities/systemmanagement/administratortype.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const AdministratorTypeSpecificFields = {
NUMBER: "TypeId",
@@ -47,6 +48,28 @@ export const getColumns = (t) => [
b[AdministratorTypeFields.NAME],
),
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/menu.entity.js b/src/entities/systemmanagement/menu.entity.js
similarity index 69%
rename from src/entities/menu.entity.js
rename to src/entities/systemmanagement/menu.entity.js
index 3a93831e4f2be9aee21daaadede13618110ac56f..f37a7a71d0848e3d25210ddcfbf874acc99ac4ce 100644
--- a/src/entities/menu.entity.js
+++ b/src/entities/systemmanagement/menu.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const MenuSpecificFields = {
KEY: "Key",
@@ -64,6 +65,28 @@ export const getColumns = (t) => [
dataIndex: MenuFields.ICON,
key: MenuFields.ICON,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/entities/role.entity.js b/src/entities/systemmanagement/role.entity.js
similarity index 61%
rename from src/entities/role.entity.js
rename to src/entities/systemmanagement/role.entity.js
index caaa0396387539ae17f426bfc09759d86880f8af..e516322aabc320e2431c4c8d957671f113d8e037 100644
--- a/src/entities/role.entity.js
+++ b/src/entities/systemmanagement/role.entity.js
@@ -1,4 +1,5 @@
-import { BaseFields, BaseInitialValues } from "./common.entity";
+import { BaseFields, BaseInitialValues } from "@/entities/common.entity";
+import { formatDateTime } from "@/utils/index";
export const RoleSpecificFields = {
NUMBER: "RoleNumber",
@@ -47,6 +48,28 @@ export const getColumns = (t) => [
dataIndex: RoleFields.DESCRIPTION,
key: RoleFields.DESCRIPTION,
},
+ {
+ title: t("message.createdBy"),
+ dataIndex: BaseFields.DATA_INS_USER,
+ key: BaseFields.DATA_INS_USER,
+ },
+ {
+ title: t("message.createdTime"),
+ dataIndex: BaseFields.DATA_INS_DATE,
+ key: BaseFields.DATA_INS_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
+ {
+ title: t("message.lastUpdatedBy"),
+ dataIndex: BaseFields.DATA_CHG_USER,
+ key: BaseFields.DATA_CHG_USER,
+ },
+ {
+ title: t("message.lastUpdatedTime"),
+ dataIndex: BaseFields.DATA_CHG_DATE,
+ key: BaseFields.DATA_CHG_DATE,
+ customRender: ({ text }) => formatDateTime(text),
+ },
];
export const getFormRules = (t) => ({
diff --git a/src/filters/department.filter.config.js b/src/filters/base/department.filter.config.js
similarity index 95%
rename from src/filters/department.filter.config.js
rename to src/filters/base/department.filter.config.js
index 5de0d6b0eaae74d1cf1e4ec028a96dc293327ba2..cf176d3a8953dbfce02d83038f83e9fa6a4850d4 100644
--- a/src/filters/department.filter.config.js
+++ b/src/filters/base/department.filter.config.js
@@ -1,4 +1,4 @@
-import { DepartmentFields } from "@/entities/department.entity";
+import { DepartmentFields } from "@/entities/base/department.entity";
import dayjs from "dayjs";
export const getDepartmentFilterConfig = (
diff --git a/src/filters/nation.filter.config.js b/src/filters/base/nation.filter.config.js
similarity index 82%
rename from src/filters/nation.filter.config.js
rename to src/filters/base/nation.filter.config.js
index 8f8e891ee1cf5cd73c391113cc73a2260455eca3..3b66873b396c7489d2552320dda3138154c348c4 100644
--- a/src/filters/nation.filter.config.js
+++ b/src/filters/base/nation.filter.config.js
@@ -1,4 +1,4 @@
-import { NationFields } from "@/entities/nation.entity";
+import { NationFields } from "@/entities/base/nation.entity";
export const getNationFilterConfig = (t) => {
return {
diff --git a/src/filters/noticetype.filter.config.js b/src/filters/base/noticetype.filter.config.js
similarity index 81%
rename from src/filters/noticetype.filter.config.js
rename to src/filters/base/noticetype.filter.config.js
index a8cedf11e5d49fa08be4680a37f70a3d3316d3e3..90683cc583195235e999c48bddc9b79db9873404 100644
--- a/src/filters/noticetype.filter.config.js
+++ b/src/filters/base/noticetype.filter.config.js
@@ -1,4 +1,4 @@
-import { NoticeTypeFields } from "@/entities/noticetype.entity";
+import { NoticeTypeFields } from "@/entities/base/noticetype.entity";
export const getNoticeTypeFilterConfig = (t) => {
return {
diff --git a/src/filters/passport.filter.config.js b/src/filters/base/passport.filter.config.js
similarity index 82%
rename from src/filters/passport.filter.config.js
rename to src/filters/base/passport.filter.config.js
index e35bb7ed509dd2730f59e42ea7344c092a240a52..052a97b428a1a17f0db84cab537c995f12a453eb 100644
--- a/src/filters/passport.filter.config.js
+++ b/src/filters/base/passport.filter.config.js
@@ -1,4 +1,4 @@
-import { PassportFields } from "@/entities/passport.entity";
+import { PassportFields } from "@/entities/base/passport.entity";
export const getPassportFilterConfig = (t) => {
return {
diff --git a/src/filters/position.filter.config.js b/src/filters/base/position.filter.config.js
similarity index 82%
rename from src/filters/position.filter.config.js
rename to src/filters/base/position.filter.config.js
index 2ba9165eabdbf48d3c38622585d3daca449f28cc..c07b6d1118bc85e83ec255b7d002bc8c875be109 100644
--- a/src/filters/position.filter.config.js
+++ b/src/filters/base/position.filter.config.js
@@ -1,4 +1,4 @@
-import { PositionFields } from "@/entities/position.entity";
+import { PositionFields } from "@/entities/base/position.entity";
export const getPositionFilterConfig = (t) => {
return {
diff --git a/src/filters/promotioncontent.filter.config.js b/src/filters/base/promotioncontent.filter.config.js
similarity index 87%
rename from src/filters/promotioncontent.filter.config.js
rename to src/filters/base/promotioncontent.filter.config.js
index 5ae6166aae895b98e0d2b02790ef51bd3374a91c..21febc81a9e2548818ed0daf181f86b81a2b5086 100644
--- a/src/filters/promotioncontent.filter.config.js
+++ b/src/filters/base/promotioncontent.filter.config.js
@@ -1,4 +1,4 @@
-import { PromotionContentFields } from "@/entities/promotionContent.entity";
+import { PromotionContentFields } from "@/entities/base/promotioncontent.entity";
export const getPromotionContentFilterConfig = (t) => {
return {
diff --git a/src/filters/qualification.filter.config.js b/src/filters/base/qualification.filter.config.js
similarity index 80%
rename from src/filters/qualification.filter.config.js
rename to src/filters/base/qualification.filter.config.js
index 1bc18c9ad0499df0311821df781d5a9486176a94..b7ce4fbac2db986836b72e740aa1e17339397bba 100644
--- a/src/filters/qualification.filter.config.js
+++ b/src/filters/base/qualification.filter.config.js
@@ -1,4 +1,4 @@
-import { EducationSpecificFields } from "@/entities/qualification.entity";
+import { EducationSpecificFields } from "@/entities/base/qualification.entity";
export const getQualificationFilterConfig = (t) => {
return {
diff --git a/src/filters/customer.filter.config.js b/src/filters/customermanagement/customer.filter.config.js
similarity index 86%
rename from src/filters/customer.filter.config.js
rename to src/filters/customermanagement/customer.filter.config.js
index 8602f456fca8753527b3259672588d936f316664..8ca5ecbcdece1e65b2b8f56a0848dd89e87c4b71 100644
--- a/src/filters/customer.filter.config.js
+++ b/src/filters/customermanagement/customer.filter.config.js
@@ -1,4 +1,4 @@
-import { CustomerFields } from "@/entities/customer.entity";
+import { CustomerFields } from "@/entities/customermanagement/customer.entity";
export const getCustomerFilterConfig = (t, customerTypeOptions) => {
return {
@@ -16,12 +16,12 @@ export const getCustomerFilterConfig = (t, customerTypeOptions) => {
label: t("message.customerName"),
placeholder: t("message.pleaseInputCustomerName"),
},
- {
- name: CustomerFields.PHONE,
- type: "text",
- label: t("message.customerTel"),
- placeholder: t("message.pleaseInputCustomerTel"),
- },
+ // {
+ // name: CustomerFields.PHONE,
+ // type: "text",
+ // label: t("message.customerTel"),
+ // placeholder: t("message.pleaseInputCustomerTel"),
+ // },
{
name: CustomerFields.TYPE,
type: "select",
diff --git a/src/filters/customertype.filter.config.js b/src/filters/customermanagement/customertype.filter.config.js
similarity index 78%
rename from src/filters/customertype.filter.config.js
rename to src/filters/customermanagement/customertype.filter.config.js
index 1b9a426103fdc5953998e29cf93ee8249912c58f..29de7f9cb20e54427058e8ff613ffd8ff1677293 100644
--- a/src/filters/customertype.filter.config.js
+++ b/src/filters/customermanagement/customertype.filter.config.js
@@ -1,4 +1,4 @@
-import { CustomerTypeFields } from "@/entities/customertype.entity";
+import { CustomerTypeFields } from "@/entities/customermanagement/customertype.entity";
export const getCustomerTypeFilterConfig = (t) => {
return {
diff --git a/src/filters/spendinfo.filter.config.js b/src/filters/customermanagement/spendinfo.filter.config.js
similarity index 93%
rename from src/filters/spendinfo.filter.config.js
rename to src/filters/customermanagement/spendinfo.filter.config.js
index f13b38957b6ceb2149acf31c7006d72c118a7fde..bfa25be34bf9c2f2137bbdd437a6df9f3f9b2047 100644
--- a/src/filters/spendinfo.filter.config.js
+++ b/src/filters/customermanagement/spendinfo.filter.config.js
@@ -1,4 +1,5 @@
-import { SpendInfoFields } from "@/entities/spendinfo.entity";
+import { SpendInfoFields } from "@/entities/customermanagement/spendinfo.entity";
+import dayjs from "dayjs";
export const getSpendInfoFilterConfig = (t) => {
return {
diff --git a/src/filters/viprule.filter.config.js b/src/filters/customermanagement/viprule.filter.config.js
similarity index 87%
rename from src/filters/viprule.filter.config.js
rename to src/filters/customermanagement/viprule.filter.config.js
index 3ba7f6a85e491de6788151a78f828abb7cfc536b..31ef37c491a233a45cc5354b337fd66eca2b19ba 100644
--- a/src/filters/viprule.filter.config.js
+++ b/src/filters/customermanagement/viprule.filter.config.js
@@ -1,4 +1,4 @@
-import { VipRuleFields } from "@/entities/viprule.entity";
+import { VipRuleFields } from "@/entities/customermanagement/viprule.entity";
export const getVipRuleFilterConfig = (t, customerTypeOptions) => {
return {
diff --git a/src/filters/internalfinance.filter.config.js b/src/filters/finance/internalfinance.filter.config.js
similarity index 73%
rename from src/filters/internalfinance.filter.config.js
rename to src/filters/finance/internalfinance.filter.config.js
index 5e2593d9ba77d5e67b284fc94c92b94147b0b33a..90b7d98675db7ac02b75f7bd0d07057c1058a6e4 100644
--- a/src/filters/internalfinance.filter.config.js
+++ b/src/filters/finance/internalfinance.filter.config.js
@@ -1,4 +1,4 @@
-import { InternalFinanceFields } from "@/entities/internalfinance.entity";
+import { InternalFinanceFields } from "@/entities/finance/internalfinance.entity";
import dayjs from "dayjs";
export const getInternalFinanceFilterConfig = (
@@ -30,7 +30,7 @@ export const getInternalFinanceFilterConfig = (
options: personOptions,
},
{
- name: InternalFinanceFields.ACQUISITIONDATE,
+ name: InternalFinanceFields.DATE_RANGE_DTO,
type: "date-range",
label: t("message.internalfinanceTime"),
startPlaceholder: t("message.startDate"),
@@ -38,9 +38,12 @@ export const getInternalFinanceFilterConfig = (
filterFn: (value, itemValue, item) => {
if (!value || value.length !== 2) return true;
const [start, end] = value;
- const itemDate = dayjs(itemValue);
+ if (!itemValue || !itemValue.Start || !itemValue.End) return true;
+ const itemStart = dayjs(itemValue.Start);
+ const itemEnd = dayjs(itemValue.End);
+ // 检查数据范围是否与过滤范围重叠
return (
- itemDate.isAfter(dayjs(start)) && itemDate.isBefore(dayjs(end))
+ itemStart.isBefore(dayjs(end)) && itemEnd.isAfter(dayjs(start))
);
},
},
diff --git a/src/filters/employee.filter.config.js b/src/filters/humanresourcemanagement/employee.filter.config.js
similarity index 89%
rename from src/filters/employee.filter.config.js
rename to src/filters/humanresourcemanagement/employee.filter.config.js
index 0964cb241fe1e2fb3171b594f311240c48ca3054..25e73b920eb0f458c585962072c061230667fc8c 100644
--- a/src/filters/employee.filter.config.js
+++ b/src/filters/humanresourcemanagement/employee.filter.config.js
@@ -1,4 +1,4 @@
-import { EmployeeFields } from "@/entities/employee.entity";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
import dayjs from "dayjs";
export const getEmployeeFilterConfig = (
@@ -16,12 +16,12 @@ export const getEmployeeFilterConfig = (
label: t("message.employeeName"),
placeholder: t("message.pleaseInputEmployeeName"),
},
- {
- name: EmployeeFields.PHONENUMBER,
- type: "text",
- label: t("message.phoneNumber"),
- placeholder: t("message.pleaseInputPhoneNumber"),
- },
+ // {
+ // name: EmployeeFields.PHONENUMBER,
+ // type: "text",
+ // label: t("message.phoneNumber"),
+ // placeholder: t("message.pleaseInputPhoneNumber"),
+ // },
{
name: EmployeeFields.DEPARTMENT,
type: "select",
diff --git a/src/filters/energymanagement.filter.config.js b/src/filters/hydroelectricity/energymanagement.filter.config.js
similarity index 92%
rename from src/filters/energymanagement.filter.config.js
rename to src/filters/hydroelectricity/energymanagement.filter.config.js
index bb4dd759b7c9be1b05233ff5aeebb10391b233e0..1d1dbc4da1f94bb6c3d8052c52321b4495343854 100644
--- a/src/filters/energymanagement.filter.config.js
+++ b/src/filters/hydroelectricity/energymanagement.filter.config.js
@@ -1,4 +1,4 @@
-import { EnergyManagementFields } from "@/entities/energymanagement.entity";
+import { EnergyManagementFields } from "@/entities/hydroelectricity/energymanagement.entity";
import dayjs from "dayjs";
export const getEnergyManagementFilterConfig = (t) => {
diff --git a/src/filters/goods.filter.config.js b/src/filters/materialmanagement/goods.filter.config.js
similarity index 86%
rename from src/filters/goods.filter.config.js
rename to src/filters/materialmanagement/goods.filter.config.js
index ab48c0ca1db915429eeadafaf36132ce230ce3ac..9bc238c67ad1c7ea7aea1856e1d09a3f6037fec7 100644
--- a/src/filters/goods.filter.config.js
+++ b/src/filters/materialmanagement/goods.filter.config.js
@@ -1,4 +1,4 @@
-import { GoodsFields } from "@/entities/goods.entity";
+import { GoodsFields } from "@/entities/materialmanagement/goods.entity";
export const getGoodsFilterConfig = (t) => {
return {
diff --git a/src/filters/log.filter.config.js b/src/filters/operationmanagement/log.filter.config.js
similarity index 95%
rename from src/filters/log.filter.config.js
rename to src/filters/operationmanagement/log.filter.config.js
index 1ab9e658dfe81d2892c9573e4f3a386f780ae0a1..194b470c94a218ed43e9bc8574095ebcabf7cb45 100644
--- a/src/filters/log.filter.config.js
+++ b/src/filters/operationmanagement/log.filter.config.js
@@ -1,4 +1,4 @@
-import { LogFields } from "@/entities/log.entity";
+import { LogFields } from "@/entities/operationmanagement/log.entity";
import dayjs from "dayjs";
export const getLogFilterConfig = (t) => {
diff --git a/src/filters/requestlog.filter.config.js b/src/filters/operationmanagement/requestlog.filter.config.js
similarity index 94%
rename from src/filters/requestlog.filter.config.js
rename to src/filters/operationmanagement/requestlog.filter.config.js
index a3e190701d4b29e6594353591364b8d9f8ccfeb2..e5951818efaf7d92ce5b2182cb6fc539ce32d324 100644
--- a/src/filters/requestlog.filter.config.js
+++ b/src/filters/operationmanagement/requestlog.filter.config.js
@@ -1,4 +1,4 @@
-import { LogFields } from "@/entities/requestlog.entity";
+import { LogFields } from "@/entities/operationmanagement/requestlog.entity";
import dayjs from "dayjs";
export const getRequestLogFilterConfig = (t) => {
diff --git a/src/filters/reser.filter.config.js b/src/filters/roominformation/reser.filter.config.js
similarity index 90%
rename from src/filters/reser.filter.config.js
rename to src/filters/roominformation/reser.filter.config.js
index e21aedd7a81ddec2540101c1380531c5693ced98..0841fd49fe9dbd497412ea845a7d8af0c320ed01 100644
--- a/src/filters/reser.filter.config.js
+++ b/src/filters/roominformation/reser.filter.config.js
@@ -1,4 +1,4 @@
-import { ReserFields } from "@/entities/reser.entity";
+import { ReserFields } from "@/entities/roominformation/reser.entity";
import dayjs from "dayjs";
export const getReserFilterConfig = (t, roomOptions, reserTypeOptions) => {
@@ -25,9 +25,9 @@ export const getReserFilterConfig = (t, roomOptions, reserTypeOptions) => {
options: reserTypeOptions,
},
{
- name: ReserFields.ROOMNUMBER,
+ name: ReserFields.ROOM_ID,
type: "select",
- label: t("message.reserRoomNumber"),
+ label: t("message.roomLocator"),
placeholder: t("message.pleaseSelect"),
options: roomOptions,
},
diff --git a/src/filters/room.filter.config.js b/src/filters/roominformation/room.filter.config.js
similarity index 73%
rename from src/filters/room.filter.config.js
rename to src/filters/roominformation/room.filter.config.js
index e9a9756f3c31394746745db09cd255c63ed1ef66..7669fedcf02f942e6bf9daac1fa95c08efd7440d 100644
--- a/src/filters/room.filter.config.js
+++ b/src/filters/roominformation/room.filter.config.js
@@ -1,4 +1,4 @@
-import { RoomFields } from "@/entities/room.entity";
+import { RoomFields } from "@/entities/roominformation/room.entity";
export const getRoomFilterConfig = (t, roomTypeOptions) => {
return {
@@ -11,10 +11,16 @@ export const getRoomFilterConfig = (t, roomTypeOptions) => {
placeholder: t("message.pleaseInputRoomNo"),
},
{
- name: RoomFields.NAME,
+ name: RoomFields.AREA,
type: "text",
- label: t("message.roomName"),
- placeholder: t("message.pleaseInputRoomName"),
+ label: t("message.roomArea"),
+ placeholder: t("message.pleaseInputRoomArea"),
+ },
+ {
+ name: RoomFields.FLOOR,
+ type: "number",
+ label: t("message.roomFloor"),
+ placeholder: t("message.pleaseInputRoomFloor"),
},
{
name: RoomFields.TYPE,
diff --git a/src/filters/roomconfig.filter.config.js b/src/filters/roominformation/roomconfig.filter.config.js
similarity index 79%
rename from src/filters/roomconfig.filter.config.js
rename to src/filters/roominformation/roomconfig.filter.config.js
index 0b7712e67ad31c574e70692ae1674e0ee09026c6..6321f8f093c089318fa081306828ba227731fdd7 100644
--- a/src/filters/roomconfig.filter.config.js
+++ b/src/filters/roominformation/roomconfig.filter.config.js
@@ -1,4 +1,4 @@
-import { RoomConfigFields } from "@/entities/roomconfig.entity";
+import { RoomConfigFields } from "@/entities/roominformation/roomconfig.entity";
export const getRoomConfigFilterConfig = (t) => {
return {
diff --git a/src/components/common/filterConfigBuilder.js b/src/filters/shared/filterConfigBuilder.js
similarity index 97%
rename from src/components/common/filterConfigBuilder.js
rename to src/filters/shared/filterConfigBuilder.js
index 8fa70fb205a7c8ad8debcb2ba4a8b61ebc44229a..695706649a633b2007dd9080478d85ba2bdb6591 100644
--- a/src/components/common/filterConfigBuilder.js
+++ b/src/filters/shared/filterConfigBuilder.js
@@ -1,5 +1,5 @@
import { dateFieldConfig } from "@/config/dateFields";
-import { BaseFields } from "@/common.entity";
+import { BaseFields } from "@/entities/common.entity";
const BASE_EXCLUDE = new Set([
BaseFields.ID,
diff --git a/src/filters/supervision.filter.config.js b/src/filters/supervision/supervision.filter.config.js
similarity index 57%
rename from src/filters/supervision.filter.config.js
rename to src/filters/supervision/supervision.filter.config.js
index da0e923b9d7ae6bb2a528acb30271c39bc309601..32a771d6f76d3cb5d93343f3bc449644fc2789b3 100644
--- a/src/filters/supervision.filter.config.js
+++ b/src/filters/supervision/supervision.filter.config.js
@@ -1,4 +1,4 @@
-import { SupervisionFields } from "@/entities/supervision.entity";
+import { SupervisionFields } from "@/entities/supervision/supervision.entity";
export const getSupervisionFilterConfig = (t, departmentOptions) => {
return {
@@ -35,6 +35,24 @@ export const getSupervisionFilterConfig = (t, departmentOptions) => {
label: t("message.checkAdvice"),
placeholder: t("message.pleaseInputCheckAdvice"),
},
+ {
+ name: SupervisionFields.DATE_RANGE_DTO,
+ type: "date-range",
+ label: t("message.checkDate"),
+ startPlaceholder: t("message.startDate"),
+ endPlaceholder: t("message.endDate"),
+ filterFn: (value, itemValue, item) => {
+ if (!value || value.length !== 2) return true;
+ const [start, end] = value;
+ if (!itemValue || !itemValue.Start || !itemValue.End) return true;
+ const itemStart = dayjs(itemValue.Start);
+ const itemEnd = dayjs(itemValue.End);
+ // 检查数据范围是否与过滤范围重叠
+ return (
+ itemStart.isBefore(dayjs(end)) && itemEnd.isAfter(dayjs(start))
+ );
+ },
+ },
],
};
};
diff --git a/src/filters/administatortype.filter.config.js b/src/filters/systemmanagement/administatortype.filter.config.js
similarity index 76%
rename from src/filters/administatortype.filter.config.js
rename to src/filters/systemmanagement/administatortype.filter.config.js
index c785ee833427c6648c5b39975e5ba392160f7177..1ce96c221dc894f1cd1dfb6294794863e34b0a28 100644
--- a/src/filters/administatortype.filter.config.js
+++ b/src/filters/systemmanagement/administatortype.filter.config.js
@@ -1,4 +1,4 @@
-import { AdministratorTypeFields } from "@/entities/administratortype.entity";
+import { AdministratorTypeFields } from "@/entities/systemmanagement/administratortype.entity";
export const getAdminTypeFilterConfig = (t) => {
return {
diff --git a/src/filters/administrator.filter.config.js b/src/filters/systemmanagement/administrator.filter.config.js
similarity index 89%
rename from src/filters/administrator.filter.config.js
rename to src/filters/systemmanagement/administrator.filter.config.js
index 8d774d96bb293f44f958c1c2a348de56e15cf50b..002eceea051bdca7094090e7ce13d7d61c596d2e 100644
--- a/src/filters/administrator.filter.config.js
+++ b/src/filters/systemmanagement/administrator.filter.config.js
@@ -1,4 +1,4 @@
-import { AdministratorFields } from "@/entities/administrator.entity";
+import { AdministratorFields } from "@/entities/systemmanagement/administrator.entity";
export const getAdministratorFilterConfig = (t, adminTypeOptions) => {
return {
diff --git a/src/filters/menu.filter.config.js b/src/filters/systemmanagement/menu.filter.config.js
similarity index 80%
rename from src/filters/menu.filter.config.js
rename to src/filters/systemmanagement/menu.filter.config.js
index 2d65bafb8e3eba10f816af11d07f6b0f9916ddff..91d1d27caf780136961de671f248a76654678de7 100644
--- a/src/filters/menu.filter.config.js
+++ b/src/filters/systemmanagement/menu.filter.config.js
@@ -1,4 +1,4 @@
-import { MenuFields } from "@/entities/menu.entity";
+import { MenuFields } from "@/entities/systemmanagement/menu.entity";
export const getMenuFilterConfig = (t) => {
return {
diff --git a/src/filters/role.filter.config.js b/src/filters/systemmanagement/role.filter.config.js
similarity index 86%
rename from src/filters/role.filter.config.js
rename to src/filters/systemmanagement/role.filter.config.js
index 8dd2bdb7ad0a38459e137a4eaf3698ef7bb9c2c2..aa9ce5449102e4d3c926f7efb3f5ec15fa21af7c 100644
--- a/src/filters/role.filter.config.js
+++ b/src/filters/systemmanagement/role.filter.config.js
@@ -1,4 +1,4 @@
-import { RoleFields } from "@/entities/role.entity";
+import { RoleFields } from "@/entities/systemmanagement/role.entity";
export const getRoleFilterConfig = (t) => {
return {
diff --git a/src/i18n.js b/src/i18n.js
index bba2efcf423d5eeffbdc4a98b88adcc7ff89aea3..fdb92a91ced9090697397f4d3fa26310cf0491e7 100644
--- a/src/i18n.js
+++ b/src/i18n.js
@@ -3,6 +3,7 @@ import { createI18n } from "vue-i18n";
const messages = {
"en-US": {
message: {
+ all: "All",
hello: "hello",
welcome: "Welcome to our application",
username: "Username",
@@ -13,6 +14,18 @@ const messages = {
signup: "Sign Up",
logout: "Log out",
home: "Home",
+ favoriteCollection: "Favorites",
+ addToFavoriteCollection: "Add to favorites",
+ removeFromFavoriteCollection: "Remove from favorites",
+ favoriteAdded: "Page added to favorites",
+ favoriteRemoved: "Page removed from favorites",
+ savingFavoriteCollection: "Saving favorites, please wait...",
+ favoriteCollectionIdentityChanged:
+ "Login identity changed. Refresh the page and try again.",
+ favoriteCollectionConflictRefreshed:
+ "Favorites changed in another page. The latest server state has been reloaded.",
+ favoriteCollectionSyncFailed:
+ "Failed to sync favorites. Please try again later.",
operation: "Operation",
edit: "Edit",
delete: "Delete",
@@ -28,6 +41,22 @@ const messages = {
success: "Success",
error: "Error",
undefined: "Undefined",
+ check: "Check",
+ createdBy: "Created By",
+ createdTime: "Created Time",
+ lastUpdatedBy: "Last Updated By",
+ lastUpdatedTime: "Last Updated Time",
+ copyCustomerNumber: "Copy Customer Number",
+ customerNumberCopied: "Customer number copied",
+ customerNotFound: "Customer information not found",
+ customerNotChecked:
+ "Please click the check button to confirm if the customer exists",
+ roomDataMissing: "Room data is missing. Please reopen the dialog.",
+ customerFound: "Customer information found",
+ checkinNeedCustomerRegistration:
+ "Please complete customer registration before check-in. Redirecting to Customer Management.",
+ reservationNotFoundForRoom:
+ "No available reservation was found for the selected room.",
my: "My",
passwordEditTip:
"Do not change this field if you do not want to change your password.",
@@ -38,6 +67,8 @@ const messages = {
avatarUpdateSuccess: "Avatar Update Success",
selectedRecordNotFound: "Selected record not found",
uploadImageOnly: "Only image format is supported",
+ avatarInvalidFile:
+ "Only JPG / PNG images are supported. Please choose a valid image file.",
imageSizeLimit: "Image size cannot exceed 1MB",
loading: "Loading...",
verifyCsrfTokenFailed: "Failed to verify CSRF Token",
@@ -47,10 +78,23 @@ const messages = {
batchDeleteSuccess: "Batch Delete Success",
resetPassword: "Reset Password",
selectYourLang: "Please choose your language",
+ lightMode: "Light",
+ darkMode: "Dark",
+ switchToDarkMode: "Switch to dark mode",
+ switchToLightMode: "Switch to light mode",
accountType: "Account Type",
pleaseSelectAccountType: "Please select account type",
accountTypeAdmin: "Admin",
accountTypeEmployee: "Employee",
+ employeeCheckIn: "Check In",
+ employeeCheckedIn: "Checked In",
+ morningShift: "Morning Shift",
+ eveningShift: "Evening Shift",
+ employeeShiftCheckedIn: "{shift} Checked In",
+ employeeCheckInSuccess: "Check-in recorded.",
+ employeeCheckInAlreadyDone: "Current shift already checked in.",
+ employeeCheckInIdentityMissing:
+ "Current employee identity is missing. Unable to check in.",
twoFactor: "2FA",
twoFactorVerification: "Two-Factor Verification",
twoFactorAuthentication: "Two-Factor Authentication",
@@ -94,6 +138,15 @@ const messages = {
permissionCheckError:
"Permission check failed, Please retry or contact administrator.",
refreshData: "Refresh Data",
+ exportData: "Export Data",
+ exportCurrentPage: "Export Current Page",
+ exportAllPages: "Export All Pages",
+ exportSuccess: "Export Success",
+ exportFailed: "Export Failed",
+ exportEmpty: "No data available for export",
+ exportColumnEmpty: "No exportable columns were found",
+ exportAllPages: "Export all pages data",
+ exportCurrentPage: "Export current page data",
addSuccess: "Add Success",
updateSuccess: "Update Success",
deleteSuccess: "Delete Success",
@@ -137,6 +190,45 @@ const messages = {
loginExpired: "Login expired, please log in again",
unexpectedError: "An unexpected error occurred",
serverVersion: "Server Version",
+ profileCenter: "Profile Center",
+ accountAvatar: "Account Avatar",
+ accountInfo: "Account Info",
+ accountSecurity: "Account Security",
+ securityOverview: "Security Overview",
+ rolePermissionCodes: "Role & Permission Codes",
+ roleCodes: "Role Codes",
+ directPermissionCodes: "Direct Permission Codes",
+ effectivePermissionCodes: "Effective Permission Codes",
+ permissionSource: "Permission Source",
+ permissionSourceRole: "Granted by Role",
+ permissionSourceDirect: "Granted Directly",
+ permissionSourceRoleAndDirect: "Role + Direct",
+ permissionSourceCache: "Local Cache",
+ profilePermissionCacheFallback:
+ "Permission detail APIs are unavailable. Showing locally cached effective permission codes.",
+ accountAvatarUploadUnsupported:
+ "The current account type has no avatar upload API yet. The backend adaptation document lists the required interface.",
+ profileAvatarUploadTip: "Supported formats: JPG / PNG, maximum size 1MB.",
+ passwordChangeApiPending: "Password change API is not connected yet.",
+ passwordChangeApiPendingDetail:
+ "The frontend reserves the account security section. A backend contract document has been added for self-service password change and admin avatar support.",
+ oldPassword: "Old Password",
+ newPassword: "New Password",
+ confirmPassword: "Confirm Password",
+ changePassword: "Change Password",
+ passwordChangeTip:
+ "After changing the password, the current session will be cleared and you need to sign in again.",
+ pleaseInputOldPassword: "Please input the current password",
+ pleaseInputNewPassword: "Please input the new password",
+ pleaseInputConfirmPassword: "Please confirm the new password",
+ passwordConfirmMismatch:
+ "The confirmation password does not match the new password.",
+ passwordMinLength: "Password must be at least 6 characters.",
+ passwordMustDiffer:
+ "The new password must be different from the current password.",
+ passwordChangeFailed: "Failed to change password.",
+ passwordChangeSuccessRelogin:
+ "Password changed successfully. Please sign in again.",
homeAccountLabel: "Account",
homeUpdatedAtLabel: "Updated At",
homeAdminDetailPermissionFallback:
@@ -236,6 +328,7 @@ const messages = {
deleteDepartment: "Delete Department",
pleaseInputDepartmentName: "Please input department name",
pleaseInputDepartmentNo: "Please input department no",
+ pleaseInputDepartmentDesc: "Please input department description",
pleaseInputDepartmentLeader: "Please select department leader",
pleaseInputDepartmentParent: "Please select department parent",
departmentNo: "Department No",
@@ -347,6 +440,7 @@ const messages = {
pleaseInputCheckScore: "Please input SupervisionInfo Check Score",
pleaseInputCheckPerson: "Please input SupervisionInfo Check Person",
pleaseInputCheckAdvice: "Please input SupervisionInfo Check Advice",
+ pleaseInputCheckDate: "Please input SupervisionInfo Check Date",
checkNo: "SupervisionInfo No",
checkDepartment: "Check Department",
checkProgres: "Check Progress",
@@ -354,6 +448,7 @@ const messages = {
checkScore: "Check Score",
checkPerson: "Check Person",
checkAdvice: "Check Advice",
+ checkDate: "Check Date",
supervisionFilter: "Supervision Filter",
dataInsDateStart: "Start Date",
dataInsDateEnd: "End Date",
@@ -371,9 +466,14 @@ const messages = {
pleaseInputRoomState: "Please input Room State",
pleaseInputRoomRent: "Please input Room Rent",
pleaseInputRoomDeposit: "Please input Room Deposit",
- pleaseInputRoomPosition: "Please input Room Position",
+ pleaseInputRoomArea: "Please input Room Area",
+ pleaseInputRoomFloor: "Please input Room Floor",
+ pleaseInputRoomLocation: "Please input Room Location",
+ roomArea: "Room Area",
+ roomFloor: "Room Floor",
+ roomLocator: "Room Locator",
roomState: "Room State",
- roomPosition: "Room Position",
+ roomLocation: "Room Location",
roomType: "Room Type",
roomFilter: "Room Filter",
roomName: "Room Name",
@@ -416,10 +516,15 @@ const messages = {
pleaseInputCustomerPassportType: "Please input customer passport type",
pleaseInputCustomerPassportID: "Please input customer passport ID",
pleaseInputCustomerAddress: "Please input customer address",
+ pleaseInputDetailAddress: "Please input detail address",
+ pleaseSelectRegion: "Please select region",
pleaseInputCustomerBirth: "Please input customer birthday",
pleaseInputCustomerType: "Please input customer type",
invalidPhone: "Invalid Phone Number",
invalidIdNumber: "Invalid ID Number",
+ invalidCustomerBirth: "Invalid Birth Date",
+ invalidCustomerPassportType: "Invalid Passport Type",
+ invalidCustomerType: "Invalid Customer Type",
customerNo: "Customer No",
customerName: "Customer Name",
customerGender: "Customer Gender",
@@ -450,6 +555,7 @@ const messages = {
updateSpend: "Edit Spending Bill",
viewSpend: "View Spending Bill",
deleteSpend: "Delete Spending Bill",
+ pleaseEnter: "Please enter",
pleaseInputSpendName: "Please input customer name",
pleaseInputSpendAmount: "Please input customer Gender",
pleaseInputSpendPrice: "Please input customer telephone number",
@@ -467,6 +573,8 @@ const messages = {
insertStaff: "Create Staff",
updateStaff: "Edit Staff",
+ insertWorkHistory: "Add Work History",
+ updateWorkHistory: "Edit Work History",
viewStaff: "View Staff",
pleaseInputStaffName: "Please input staff name",
pleaseInputEmployeeName: "Please input employee name",
@@ -595,6 +703,7 @@ const messages = {
deleteAdminType: "Delete Administrator Type",
pleaseInputAdminTypeNumber: "Please input administrator type number",
pleaseInputAdminTypeName: "Please input administrator type name",
+ pleaseInputAdminTypeDesc: "Please input administrator type description",
adminTypeNumber: "Administrator Type Number",
adminTypeName: "Administrator Type Name",
adminTypeFilter: "Administrator Type Filter",
@@ -633,6 +742,24 @@ const messages = {
permissionName: "Permission Name",
menuFilter: "Menu Filter",
+ quartzjoblist: "Quartz Job List",
+ jobName: "Job Name",
+ jobGroup: "Job Group",
+ triggerName: "Trigger Name",
+ triggerGroup: "Trigger Group",
+ triggerState: "Trigger State",
+ cronExpression: "Cron Expression",
+ nextFireTime: "Next Fire Time",
+ previousFireTime: "Previous Fire Time",
+ timeZone: "Time Zone",
+ quartzJobFilter: "Quartz Job Filter",
+ pleaseInputJobName: "Please input job name",
+ pleaseInputTriggerName: "Please input trigger name",
+ quartzStateNormal: "Normal",
+ quartzStatePaused: "Paused",
+ quartzStateBlocked: "Blocked",
+ quartzStateError: "Error",
+
promotioncontent: "Promotion Content Management",
insertPromotionContent: "Create Promotion Content",
updatePromotionContent: "Edit Promotion Content",
@@ -683,6 +810,29 @@ const messages = {
checkOutTime: "Check-out Time",
roomRent: "Room Rent",
roomDeposit: "Room Deposit",
+ currentPricing: "Current Pricing",
+ currentRoomRent: "Current Room Rent",
+ currentRoomDeposit: "Current Deposit",
+ standardRoomRent: "Standard Room Rent",
+ standardRoomDeposit: "Standard Deposit",
+ pricingOption: "Pricing Option",
+ pleaseSelectPricingOption: "Please select a pricing option",
+ pricingItems: "Additional Pricing Items",
+ pricingItem: "Pricing Item",
+ pricingCode: "Pricing Code",
+ pricingName: "Pricing Name",
+ stayHours: "Stay Hours",
+ sort: "Sort",
+ stayHoursHint:
+ "This pricing option is intended for stays within {hours} hour(s).",
+ addPricingItem: "Add Pricing Item",
+ noPricingItems: "No additional pricing items",
+ additionalPricingItemsHint:
+ "Keep the standard rent and deposit in the original fields, and configure only extra pricing items here.",
+ pleaseInputPricingCode: "Please input pricing code",
+ pleaseInputPricingName: "Please input pricing name",
+ pricingCodeDuplicate: "Pricing code cannot be duplicated",
+ standardPrice: "Standard Price",
custoName: "Customer Name",
dayUnit: "Day",
totalRecords: "Total {total} Records",
@@ -691,12 +841,17 @@ const messages = {
normal: "INFO",
error: "CRITICAL",
warning: "WARNING",
- reserve: "Reserve",
+ reserve: "Reserve Room",
checkIn: "Check In",
transferRoom: "Transfer Room",
checkOut: "Check Out",
changeRoomState: "Change Room State",
viewCustomerInfo: "View Customer Info",
+ roomGoodsConsumption: "Room Goods Consumption",
+ recallSpend: "Recall Consumption",
+ pleaseSelectGoodsFirst: "Please select a product first",
+ pleaseSelectSpendFirst: "Please select a consumption record first",
+ roomMissingCustomerNumber: "The current room has no customer number",
roomOverview: "Room Overview",
roomStatusDistribution: "Room Status Distribution",
@@ -752,13 +907,35 @@ const messages = {
late: "Late",
absent: "Absent",
consumptionTime: "Consumption Time",
+ consumptionTotal: "Consumption Total",
+ payableAmount: "Payable Amount",
+ vipDiscount: "VIP Discount",
+ changeAmount: "Change Amount",
+ actualReceived: "Actual Received",
+ actualReceivedInsufficient:
+ "Actual received amount cannot be less than the payable amount.",
+ settle: "Settle",
+ discount: "Discount",
+ discountTextWithType: "{type} {offPercent}% off",
+ discountText: "{offPercent}% off",
+ discountTextNoDiscount: "No discount",
pleaseInputPath: "Please input path",
pleaseInputUserName: "Please input user name",
- },
- },
+
+ roomMapOperationHint:
+ "Tip: Right-click a room card to view available action. Cards with shortcut support will show a double-click hint on hover.",
+ doubleClickAction: "Double-click{action}",
+ closeTab: "Close Tab",
+ closeLeftTabs: "Close Tabs to the Left",
+ closeRightTabs: "Close Tabs to the Right",
+ closeOtherTabs: "Close Other Tabs",
+ closeAllTabs: "Close All Tabs",
+ },
+ },
"zh-CN": {
message: {
+ all: "全部",
hello: "你好",
welcome: "欢迎使用我们的应用程序",
username: "用户账号",
@@ -769,6 +946,16 @@ const messages = {
signup: "注册",
logout: "登出",
home: "主页",
+ favoriteCollection: "收藏夹",
+ addToFavoriteCollection: "添加到收藏夹",
+ removeFromFavoriteCollection: "从收藏夹中移除",
+ favoriteAdded: "页面已添加到收藏夹",
+ favoriteRemoved: "页面已从收藏夹中移除",
+ savingFavoriteCollection: "正在保存收藏夹,请稍候...",
+ favoriteCollectionIdentityChanged: "登录身份已变化,请刷新页面后重试。",
+ favoriteCollectionConflictRefreshed:
+ "收藏夹已在其他页面更新,已同步为服务端最新结果。",
+ favoriteCollectionSyncFailed: "收藏夹同步失败,请稍后重试。",
operation: "操作",
edit: "编辑",
delete: "删除",
@@ -783,6 +970,20 @@ const messages = {
success: "成功",
error: "错误",
id: "编号",
+ check: "检查",
+ createdBy: "创建人",
+ createdTime: "创建时间",
+ lastUpdatedBy: "最后更新人",
+ lastUpdatedTime: "最后更新时间",
+ copyCustomerNumber: "复制客户编号",
+ customerNumberCopied: "客户编号已复制",
+ customerNotFound: "未找到客户信息",
+ customerNotChecked: "请先点击检查按钮确认客户是否存在",
+ roomDataMissing: "房间数据缺失,请重新打开弹窗后重试",
+ customerFound: "客户信息已找到",
+ checkinNeedCustomerRegistration:
+ "请先完成客户信息注册再进行入住,系统将跳转至客户管理页面。",
+ reservationNotFoundForRoom: "未找到对应房间的有效预约信息。",
my: "我的",
passwordEditTip: "留空表示不修改密码",
required: "请输入 {field}",
@@ -792,6 +993,7 @@ const messages = {
avatarUpdateSuccess: "头像更新成功",
selectedRecordNotFound: "选中的记录未找到",
uploadImageOnly: "仅支持图片格式",
+ avatarInvalidFile: "仅支持 JPG / PNG 格式图片,请重新选择有效图片文件。",
imageSizeLimit: "图片大小不能超过 1MB",
loading: "加载中...",
verifyCsrfTokenFailed: "验证CSRF Token失败",
@@ -801,10 +1003,22 @@ const messages = {
batchDeleteSuccess: "批量删除成功",
resetPassword: "重置密码",
selectYourLang: "请选择您的语言",
+ lightMode: "浅色",
+ darkMode: "深色",
+ switchToDarkMode: "切换为深色模式",
+ switchToLightMode: "切换为浅色模式",
accountType: "账号类型",
pleaseSelectAccountType: "请选择账号类型",
accountTypeAdmin: "管理员",
accountTypeEmployee: "员工",
+ employeeCheckIn: "打卡",
+ employeeCheckedIn: "已打卡",
+ morningShift: "早班",
+ eveningShift: "晚班",
+ employeeShiftCheckedIn: "{shift}已打卡",
+ employeeCheckInSuccess: "打卡成功",
+ employeeCheckInAlreadyDone: "当前班次已打卡",
+ employeeCheckInIdentityMissing: "未找到当前员工标识,无法打卡。",
twoFactor: "两步验证入口",
twoFactorVerification: "两步验证",
twoFactorAuthentication: "两步验证设置",
@@ -842,6 +1056,13 @@ const messages = {
permissionCheckError: "权限检查发生错误,请重试或联系管理员",
refreshData: "刷新数据",
addSuccess: "添加成功",
+ exportData: "导出数据",
+ exportSuccess: "导出成功",
+ exportFailed: "导出失败",
+ exportEmpty: "没有数据可以导出",
+ exportColumnEmpty: "选中的列没有数据",
+ exportAllPages: "导出所有页数据",
+ exportCurrentPage: "导出当前页数据",
updateSuccess: "更新成功",
deleteSuccess: "删除成功",
restoreSuccess: "恢复成功",
@@ -874,6 +1095,7 @@ const messages = {
invalidCredentials: "无效的凭据",
noPermission: "无权限",
notFound: "未找到",
+ networkError: "网络错误",
validationFailed: "验证失败",
duplicate: "重复数据",
serverError: "服务器错误",
@@ -959,6 +1181,7 @@ const messages = {
deleteDepartment: "删除部门",
pleaseInputDepartmentName: "请输入部门名称",
pleaseInputDepartmentNo: "请输入部门编号",
+ pleaseInputDepartmentDesc: "请输入部门描述",
pleaseInputDepartmentLeader: "请输入部门主管",
pleaseInputDepartmentParent: "请输入上级部门",
departmentNo: "部门编号",
@@ -1069,6 +1292,7 @@ const messages = {
pleaseInputCheckScore: "请输入本次监管评分",
pleaseInputCheckPerson: "请输入本次监管人员",
pleaseInputCheckAdvice: "请输入本次监管建议",
+ pleaseInputCheckDate: "请输入监管日期",
checkNo: "监管情况编号",
checkDepartment: "受监管部门",
checkProgres: "监管进度",
@@ -1076,6 +1300,7 @@ const messages = {
checkScore: "本次评分",
checkPerson: "本次监管人员",
checkAdvice: "本次监管建议",
+ checkDate: "监管日期",
supervisionFilter: "监管筛选",
dataInsDateStart: "开始日期",
dataInsDateEnd: "结束日期",
@@ -1090,9 +1315,14 @@ const messages = {
pleaseInputRoomState: "请输入房间状态",
pleaseInputRoomRent: "请输入房间租金",
pleaseInputRoomDeposit: "请输入房间押金",
- pleaseInputRoomPosition: "请输入房间位置",
+ pleaseInputRoomArea: "请输入房间区域",
+ pleaseInputRoomFloor: "请输入房间楼层",
+ pleaseInputRoomLocation: "请输入房间位置",
+ roomArea: "房间区域",
+ roomFloor: "房间楼层",
+ roomLocator: "房间定位",
roomState: "房间状态",
- roomPosition: "房间位置",
+ roomLocation: "房间位置",
roomType: "房间类型",
roomFilter: "房间筛选",
@@ -1135,14 +1365,19 @@ const messages = {
pleaseInputCustomerPassportType: "请输入客户证件类型",
pleaseInputCustomerPassportID: "请输入客户证件号码",
pleaseInputCustomerAddress: "请输入客户地址",
+ pleaseInputDetailAddress: "请输入详细地址",
+ pleaseSelectRegion: "请选择地区",
pleaseInputCustomerBirth: "请输入客户出生日期",
pleaseInputCustomerType: "请输入客户类型",
invalidPhone: "无效的电话号码",
invalidIdNumber: "无效的身份证号码",
+ invalidCustomerBirth: "无效的出生日期",
+ invalidCustomerPassportType: "无效的证件类型",
+ invalidCustomerType: "无效的客户类型",
customerNo: "客户编号",
customerName: "客户姓名",
customerGender: "客户性别",
- customerTel: "客户电话号码",
+ customerTel: "电话号码",
customerPassportType: "证件类型",
customerPassportID: "证件号码",
customerAddress: "客户地址",
@@ -1166,9 +1401,14 @@ const messages = {
customerspend: "客户消费记录",
insertSpend: "添加消费记录",
+ recallSpend: "撤回消费",
updateSpend: "编辑消费记录",
viewSpend: "查看消费记录",
deleteSpend: "删除消费记录",
+ pleaseSelectGoodsFirst: "请先选择商品",
+ pleaseSelectSpendFirst: "请先选择一条消费记录",
+ roomMissingCustomerNumber: "当前房间缺少入住客户编号",
+ pleaseEnter: "请输入",
pleaseInputSpendName: "请输入商品名称",
pleaseInputSpendAmount: "请输入商品数量",
pleaseInputSpendPrice: "请输入单价",
@@ -1186,6 +1426,8 @@ const messages = {
insertStaff: "添加员工",
updateStaff: "编辑员工",
+ insertWorkHistory: "添加履历",
+ updateWorkHistory: "编辑履历",
viewStaff: "查看员工",
pleaseInputStaffName: "请输入员工姓名",
pleaseInputEmployeeName: "请输入员工姓名",
@@ -1313,6 +1555,7 @@ const messages = {
deleteAdminType: "删除管理员类型",
pleaseInputAdminTypeNumber: "请输入管理员类型编号",
pleaseInputAdminTypeName: "请输入管理员类型名称",
+ pleaseInputAdminTypeDesc: "请输入管理员类型描述",
adminTypeNumber: "管理员类型编号",
adminTypeName: "管理员类型名称",
adminTypeFilter: "管理员类型筛选",
@@ -1351,6 +1594,24 @@ const messages = {
permissionName: "权限名称",
menuFilter: "菜单筛选",
+ quartzjoblist: "定时任务列表",
+ jobName: "任务名称",
+ jobGroup: "任务分组",
+ triggerName: "触发器名称",
+ triggerGroup: "触发器分组",
+ triggerState: "触发器状态",
+ cronExpression: "Cron表达式",
+ nextFireTime: "下次执行时间",
+ previousFireTime: "上次执行时间",
+ timeZone: "时区",
+ quartzJobFilter: "定时任务筛选",
+ pleaseInputJobName: "请输入任务名称",
+ pleaseInputTriggerName: "请输入触发器名称",
+ quartzStateNormal: "正常",
+ quartzStatePaused: "暂停",
+ quartzStateBlocked: "阻塞",
+ quartzStateError: "异常",
+
promotioncontent: "宣传联动内容管理",
insertPromotionContent: "创建宣传联动内容",
updatePromotionContent: "编辑宣传联动内容",
@@ -1399,6 +1660,28 @@ const messages = {
checkOutTime: "退房时间",
roomRent: "房间租金",
roomDeposit: "房间押金",
+ currentPricing: "当前计价",
+ currentRoomRent: "当前房费",
+ currentRoomDeposit: "当前押金",
+ standardRoomRent: "标准价",
+ standardRoomDeposit: "标准押金",
+ pricingOption: "计价项",
+ pleaseSelectPricingOption: "请选择计价项",
+ pricingItems: "附加计价项",
+ pricingItem: "计价项",
+ pricingCode: "计价编码",
+ pricingName: "计价名称",
+ stayHours: "停留小时数",
+ sort: "排序",
+ stayHoursHint: "该计价项适用于 {hours} 小时内入住展示",
+ addPricingItem: "新增计价项",
+ noPricingItems: "暂无附加计价项",
+ additionalPricingItemsHint:
+ "标准价仍由房租和押金字段维护,这里只配置额外计价项。",
+ pleaseInputPricingCode: "请输入计价编码",
+ pleaseInputPricingName: "请输入计价名称",
+ pricingCodeDuplicate: "计价编码不能重复",
+ standardPrice: "标准价",
custoName: "客户姓名",
dayUnit: "天",
totalRecords: "共 {total} 条记录",
@@ -1407,12 +1690,13 @@ const messages = {
normal: "常规操作",
error: "严重操作",
warning: "敏感操作",
- reserve: "预约",
- checkIn: "入住",
- transferRoom: "转房",
- checkOut: "退房",
+ reserve: "预约房间",
+ checkIn: "办理入住",
+ transferRoom: "转换房间",
+ checkOut: "结算退房",
changeRoomState: "更改房间状态",
viewCustomerInfo: "查看客户信息",
+ roomGoodsConsumption: "商品消费",
roomOverview: "客房概览",
roomStatusDistribution: "房间状态分布",
@@ -1468,6 +1752,17 @@ const messages = {
late: "迟到",
absent: "旷工",
consumptionTime: "消费时间",
+ consumptionTotal: "消费总额",
+ payableAmount: "应付金额",
+ vipDiscount: "会员折扣",
+ changeAmount: "找零金额",
+ actualReceived: "实收金额",
+ actualReceivedInsufficient: "实收金额不能小于应付金额",
+ settle: "结 算",
+ discount: "折",
+ discountTextWithType: "{type} {fold}折 (优惠{offPercent}%)",
+ discountText: "{fold}折 (优惠{offPercent}%)",
+ discountTextNoDiscount: "无折扣(10折)",
pleaseInputPath: "请输入请求路径",
pleaseInputUserName: "请输入用户名",
@@ -1536,8 +1831,52 @@ const messages = {
homeAdminDetailNotFound: "管理员详情接口未返回当前账号数据。",
homeAdminProfileLoadFailed: "管理员信息加载失败",
homeEnvironmentLoadFailed: "系统环境配置加载失败",
- },
- },
+
+ profileCenter: "个人中心",
+ accountAvatar: "账号头像",
+ accountInfo: "账号信息",
+ accountSecurity: "账号安全",
+ securityOverview: "安全概览",
+ rolePermissionCodes: "角色及权限代码",
+ roleCodes: "角色编码",
+ directPermissionCodes: "直接权限编码",
+ effectivePermissionCodes: "生效权限编码",
+ permissionSource: "来源",
+ permissionSourceRole: "角色授权",
+ permissionSourceDirect: "直接授权",
+ permissionSourceRoleAndDirect: "角色 + 直接",
+ permissionSourceCache: "本地缓存",
+ profilePermissionCacheFallback:
+ "当前无法调用权限详情接口,已展示本地缓存的生效权限编码。",
+ accountAvatarUploadUnsupported:
+ "当前账号类型暂未接入头像上传接口,具体适配要求请见后端对接文档。",
+ profileAvatarUploadTip: "支持 JPG / PNG 格式,图片大小不能超过 1MB。",
+ passwordChangeApiPending: "密码修改接口尚未对接。",
+ passwordChangeApiPendingDetail:
+ "前端已预留账号安全分栏,并在 docs 中补充了自助修改密码和管理员头像接口合同。",
+ oldPassword: "当前密码",
+ newPassword: "新密码",
+ confirmPassword: "确认新密码",
+ changePassword: "修改密码",
+ passwordChangeTip: "修改密码后将清空当前登录态,需要重新登录。",
+ pleaseInputOldPassword: "请输入当前密码",
+ pleaseInputNewPassword: "请输入新密码",
+ pleaseInputConfirmPassword: "请再次输入新密码",
+ passwordConfirmMismatch: "两次输入的新密码不一致。",
+ passwordMinLength: "密码长度不能少于 6 位。",
+ passwordMustDiffer: "新密码不能与当前密码相同。",
+ passwordChangeFailed: "密码修改失败。",
+ passwordChangeSuccessRelogin: "密码修改成功,请重新登录。",
+ roomMapOperationHint:
+ "提示:对房间卡片点击鼠标右键可查看操作选项。支持快捷操作的卡片在悬停时会显示双击提示。",
+ doubleClickAction: "双击{action}",
+ closeTab: "关闭标签页",
+ closeLeftTabs: "关闭左侧标签页",
+ closeRightTabs: "关闭右侧标签页",
+ closeOtherTabs: "关闭其他标签页",
+ closeAllTabs: "关闭全部标签页",
+ },
+ },
};
const i18n = createI18n({
diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue
index 8f850bace44d061e8c78c7bedf681ad70986dd30..bbc39ddeb0dd464723d7dd96a0f3fe7f59098351 100644
--- a/src/layouts/MainLayout.vue
+++ b/src/layouts/MainLayout.vue
@@ -1,51 +1,98 @@
-
-
-
-

+
+
+
-
{{
+
+
+ {{
$t("message.systemName")
}}
-
+
+
+ {{ $t("message.serverVersion") }}: {{ serverVersion || "-" }}
+
+
+
+
{{ themeModeLabel }}
+
+ D
+ L
+
+
+
- {{
- username
- }}
+
+ {{ employeeCheckInButtonText }}
+
+
+ {{ employeeCheckInDayCountText }}
+
+ {{ username }}
+
+
+
+
+
+
{{ $t("message.twoFactor") }}
- {{
- $t("message.logout")
- }}
+ {{ $t("message.logout") }}
- {{
+ {{
$t("message.signin")
}}
简体中文
@@ -53,34 +100,67 @@
+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ favoriteSyncLoadingText }}
+
+
+
+
-
+
© {{ "2024-" + new Date().getFullYear() }} {{ $t("message.org") }}
- {{ $t("message.systemName") }} | {{ $t("message.serverVersion") }}:
- {{ serverVersion }}
+ {{ $t("message.systemName") }}
@@ -231,6 +310,7 @@
import {
ref,
computed,
+ inject,
onMounted,
watchEffect,
onBeforeMount,
@@ -238,7 +318,7 @@ import {
watch,
nextTick,
} from "vue";
-import { useStorage } from "@vueuse/core";
+import { useStorage, useMediaQuery } from "@vueuse/core";
import { useRoute, useRouter } from "vue-router";
import {
formatDateTime,
@@ -247,24 +327,35 @@ import {
showWarningNotification,
} from "@/utils/index";
import { resolveErrorMessage } from "@/common/errorHandler";
-import { fetchMenusTree } from "../api/menuapi";
+import { getPageTitle } from "@/utils/pageTitle";
import { BaseFields } from "@/entities/common.entity";
import {
readUserDirectPermissions as readAdminUserDirectPermissions,
readUserRolePermissions as readAdminUserRolePermissions,
-} from "@/api/administratorapi";
+} from "@/api/systemmanagement/administratorapi";
import {
readUserDirectPermissions as readEmployeeUserDirectPermissions,
readUserRolePermissions as readEmployeeUserRolePermissions,
-} from "@/api/employeepermissionapi";
+} from "@/api/systemmanagement/employeepermissionapi";
import {
readUserDirectPermissions as readCustomerUserDirectPermissions,
readUserRolePermissions as readCustomerUserRolePermissions,
-} from "@/api/customerpermissionapi";
+} from "@/api/systemmanagement/customerpermissionapi";
+import {
+ addEmployeeCheckIn,
+ fetchEmployeeTodayCheckIn,
+ fetchEmployees,
+} from "@/api/humanresourcemanagement/employeeapi";
+import {
+ fetchCurrentAccountProfile,
+ fetchUserProfile,
+} from "@/api/profilecenterapi";
import {
AdministratorManagementPermissions,
StaffManagementPermissions,
} from "@/common/permissioncode";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
+import { EmployeeCheckFields } from "@/entities/humanresourcemanagement/employeecheck.entity";
import { useI18n } from "vue-i18n";
import RecursiveMenu from "./Menu/RecursiveMenu.vue";
@@ -280,20 +371,45 @@ import {
regenerateTwoFactorRecoveryCodes,
signOut,
getServerVersion,
-} from "../api/basicapi";
+} from "@/api/basicapi";
import { clearRowVersionCache } from "@/api/baseapi";
-import { isPermissionDeniedErrorCode } from "@/common/errorCodes";
+import { ERROR_CODES, isPermissionDeniedErrorCode } from "@/common/errorCodes";
import { decodeJwtPayload, getUserIdentity, isTokenExpired } from "@/utils/jwt";
import {
+ bindFavoriteCollectionAutoSync,
+ clearFavoriteCollectionState,
+ ensureFavoriteCollectionOwner,
+ loadFavoriteCollection,
+ readFavoriteRoutes,
+ syncFavoriteCollection,
+ syncFavoriteCollectionOnLogout,
+ toggleFavoriteRoute,
+ unbindFavoriteCollectionAutoSync,
+ writeFavoriteRoutes,
+} from "@/utils/navigation/favoriteCollection";
+import {
+ clearStoredSessionItem,
clearStoredAllowedPathsToken,
clearStoredToken,
+ getStoredSessionItem,
getStoredAllowedPathsToken,
getStoredToken,
getStoredTokenCacheKey,
+ SESSION_STORAGE_KEYS,
+ setStoredSessionItem,
setStoredAllowedPathsToken,
} from "@/utils/tokenStorage";
+import { THEME_CONTEXT_KEY, THEME_MODES } from "@/utils/theme";
+import { createTabsContext, clearSavedTabs } from "@/composables/useTabs";
+import PageTabBar from "@/components/common/PageTabBar.vue";
const serverVersion = ref("");
+const siderCollapsed = useStorage("sider-collapsed", false);
+const isMobile = useMediaQuery("(max-width: 576px)");
+const themeContext = inject(THEME_CONTEXT_KEY, null);
+const isDarkMode = computed(() => themeContext?.isDarkMode?.value === true);
+const { locale, t } = useI18n();
+const tabsContext = createTabsContext({ t });
const fetchServerVersion = async () => {
try {
@@ -395,14 +511,28 @@ const findActiveKeys = (
const route = useRoute();
const router = useRouter();
+const FAVORITE_COLLECTION_MENU_KEY = "favorite-collection-root";
const menuData = ref([]);
+const favoriteRoutes = ref([]);
const username = ref("");
const isLoggedIn = ref(false);
-const { locale, t } = useI18n();
+const themeModeLabel = computed(() =>
+ t(isDarkMode.value ? "message.darkMode" : "message.lightMode"),
+);
+const themeSwitchTitle = computed(() =>
+ t(
+ isDarkMode.value ? "message.switchToLightMode" : "message.switchToDarkMode",
+ ),
+);
const currentLocale = ref(locale.value);
+const handleThemeChange = (checked) => {
+ themeContext?.setThemeMode?.(checked ? THEME_MODES.DARK : THEME_MODES.LIGHT);
+};
const openKeys = useStorage("menu-open-keys", []);
const currentRouteKey = ref("");
-const loginType = ref(localStorage.getItem("loginType") || "admin");
+const loginType = ref(
+ getStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE) || "admin",
+);
const allowedPermissionCodes = ref([]);
const twoFactorModalOpen = ref(false);
const twoFactorStatusLoading = ref(false);
@@ -414,7 +544,179 @@ const twoFactorDisableCode = ref("");
const twoFactorRecoveryCode = ref("");
const latestRecoveryCodes = ref([]);
const showRecoveryCodesGuide = ref(false);
+const employeeCheckInEmployeeId = ref("");
+const employeeCheckInStatusLoading = ref(false);
+const employeeCheckInSubmitting = ref(false);
+const employeeCheckedInToday = ref(false);
+const employeeCurrentShift = ref("morning");
+const employeeCheckInDayCount = ref("");
+const employeeCheckInStatusRequestId = ref(0);
+const favoriteToggleSyncLoading = ref(false);
+const favoriteBlockingSyncLoading = ref(false);
const LOW_RECOVERY_CODES_THRESHOLD = 2;
+const normalizeMenuPath = (path) => (path || "").replace(/\/$/, "");
+
+const flattenMenuItems = (items, acc = []) => {
+ for (const item of items || []) {
+ acc.push(item);
+ if (Array.isArray(item.children) && item.children.length > 0) {
+ flattenMenuItems(item.children, acc);
+ }
+ }
+ return acc;
+};
+
+const getFavoriteCollectionOwner = () => ({
+ loginType:
+ loginType.value ||
+ getStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE) ||
+ "admin",
+ account:
+ getStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT) ||
+ getStoredSessionItem(SESSION_STORAGE_KEYS.USERNAME) ||
+ "",
+});
+
+const isNavigableFavoriteRoute = (path) => {
+ const normalizedPath = normalizeMenuPath(path);
+ if (!normalizedPath || normalizedPath === "/home") {
+ return false;
+ }
+
+ const resolvedRoute = router.resolve(normalizedPath);
+ return !resolvedRoute.matched.some((record) => record.name === "NotFound");
+};
+
+const isFavoriteEligibleRoute = (path) => {
+ const normalizedPath = normalizeMenuPath(path);
+ if (["", "/", "/home", "/signin"].includes(normalizedPath)) {
+ return false;
+ }
+
+ return isNavigableFavoriteRoute(normalizedPath);
+};
+
+const syncFavoriteRoutesFromStorage = () => {
+ const owner = ensureFavoriteCollectionOwner(getFavoriteCollectionOwner());
+ const storedRoutes = readFavoriteRoutes();
+ const validRoutes = storedRoutes.filter(isFavoriteEligibleRoute);
+
+ favoriteRoutes.value = validRoutes;
+
+ if (validRoutes.length !== storedRoutes.length) {
+ writeFavoriteRoutes(validRoutes, { owner, markDirty: false });
+ }
+};
+
+const translateFavoriteMessage = (key, zhFallback, enFallback) => {
+ if (t(key)) {
+ return t(key);
+ }
+
+ return locale.value === "zh-CN" ? zhFallback : enFallback;
+};
+
+const resolveFavoriteRouteTitle = (path) => {
+ const normalizedPath = normalizeMenuPath(path);
+ const matchedMenuItem = flattenMenuItems(menuData.value).find(
+ (item) => normalizeMenuPath(item.path) === normalizedPath,
+ );
+ if (matchedMenuItem?.title) {
+ return t(matchedMenuItem.title);
+ }
+
+ const pageTitleKey = getPageTitle(normalizedPath);
+ if (pageTitleKey && pageTitleKey !== "message.systemName") {
+ return t(pageTitleKey);
+ }
+
+ const resolvedRoute = router.resolve(normalizedPath);
+ if (typeof resolvedRoute.name === "string") {
+ return resolvedRoute.name;
+ }
+
+ return normalizedPath;
+};
+
+const favoriteMenuData = computed(() => {
+ if (!isLoggedIn.value || favoriteRoutes.value.length === 0) {
+ return [];
+ }
+
+ const favoriteItems = favoriteRoutes.value
+ .filter(isFavoriteEligibleRoute)
+ .map((path) => {
+ const normalizedPath = normalizeMenuPath(path);
+ const matchedMenuItem = flattenMenuItems(menuData.value).find(
+ (item) => normalizeMenuPath(item.path) === normalizedPath,
+ );
+
+ return {
+ key: normalizedPath,
+ menuKey: `favorite:${normalizedPath}`,
+ title: matchedMenuItem?.title || "",
+ label: resolveFavoriteRouteTitle(normalizedPath),
+ path: normalizedPath,
+ icon: matchedMenuItem?.icon || "StarOutlined",
+ };
+ });
+
+ if (favoriteItems.length === 0) {
+ return [];
+ }
+
+ return [
+ {
+ key: FAVORITE_COLLECTION_MENU_KEY,
+ menuKey: FAVORITE_COLLECTION_MENU_KEY,
+ title: "message.favoriteCollection",
+ label: translateFavoriteMessage(
+ "message.favoriteCollection",
+ "收藏夹",
+ "Favorites",
+ ),
+ icon: "StarOutlined",
+ children: favoriteItems,
+ },
+ ];
+});
+
+const displayMenuData = computed(() => [
+ ...favoriteMenuData.value,
+ ...menuData.value,
+]);
+
+const currentNormalizedRoutePath = computed(() =>
+ normalizeMenuPath(route.path),
+);
+const isCurrentRouteFavorited = computed(() =>
+ favoriteRoutes.value.includes(currentNormalizedRoutePath.value),
+);
+const showFavoriteAction = computed(
+ () =>
+ isLoggedIn.value &&
+ isFavoriteEligibleRoute(currentNormalizedRoutePath.value),
+);
+const favoriteActionTitle = computed(() =>
+ isCurrentRouteFavorited.value
+ ? translateFavoriteMessage(
+ "message.removeFromFavoriteCollection",
+ "取消收藏",
+ "Remove from favorites",
+ )
+ : translateFavoriteMessage(
+ "message.addToFavoriteCollection",
+ "收藏当前页面",
+ "Add to favorites",
+ ),
+);
+const favoriteSyncLoadingText = computed(() =>
+ translateFavoriteMessage(
+ "message.savingFavoriteCollection",
+ "正在保存收藏夹内容,请稍候...",
+ "Saving favorites, please wait...",
+ ),
+);
const showLowRecoveryCodesWarning = computed(() => {
if (!twoFactorStatus.value?.IsEnabled) return false;
const remaining = Number(twoFactorStatus.value?.RemainingRecoveryCodes);
@@ -423,6 +725,40 @@ const showLowRecoveryCodesWarning = computed(() => {
);
});
const recoveryCodesText = computed(() => latestRecoveryCodes.value.join("\n"));
+const showEmployeeCheckInEntry = computed(
+ () => isLoggedIn.value && loginType.value === "employee",
+);
+const employeeCheckInButtonLoading = computed(
+ () => employeeCheckInStatusLoading.value || employeeCheckInSubmitting.value,
+);
+const employeeCheckInDisabled = computed(
+ () =>
+ employeeCheckedInToday.value ||
+ (!employeeCheckInEmployeeId.value && !employeeCheckInButtonLoading.value),
+);
+const employeeCurrentShiftLabel = computed(() =>
+ employeeCurrentShift.value === "morning"
+ ? t("message.morningShift")
+ : t("message.eveningShift"),
+);
+const employeeCurrentShiftActionLabel = computed(() =>
+ employeeCurrentShift.value === "morning"
+ ? t("message.morningPresent")
+ : t("message.eveningPresent"),
+);
+const employeeCheckInButtonText = computed(() =>
+ employeeCheckedInToday.value
+ ? t("message.employeeShiftCheckedIn", {
+ shift: employeeCurrentShiftLabel.value,
+ })
+ : employeeCurrentShiftActionLabel.value,
+);
+const showEmployeeCheckInDayCount = computed(
+ () => employeeCheckedInToday.value && employeeCheckInDayCount.value !== "",
+);
+const employeeCheckInDayCountText = computed(
+ () => `${t("message.daySum")}: ${employeeCheckInDayCount.value}`,
+);
const TWO_FACTOR_PERMISSION_BY_LOGIN_TYPE = {
admin: [
@@ -443,7 +779,7 @@ const TWO_FACTOR_PERMISSION_BY_LOGIN_TYPE = {
const parseStoredAllowedPermissions = () => {
try {
- const rawPerms = localStorage.getItem("allowedPerms");
+ const rawPerms = getStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PERMS);
const parsedPerms = rawPerms ? JSON.parse(rawPerms) : [];
return Array.isArray(parsedPerms) ? parsedPerms : [];
} catch (error) {
@@ -465,12 +801,12 @@ const extractPermissionCodes = (items) =>
);
const clearAllowedPathsCache = () => {
- localStorage.removeItem("allowedPaths");
- localStorage.removeItem("allowedMenuKeys");
- localStorage.removeItem("allowedPerms");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_MENU_KEYS);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PERMS);
clearStoredAllowedPathsToken();
- localStorage.removeItem("allowedPathsExpire");
- localStorage.removeItem("allowedPathsIssuedAt");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS_EXPIRE);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS_ISSUED_AT);
};
const normalizePath = (path) => (path || "").replace(/\/$/, "");
@@ -480,9 +816,18 @@ const persistAllowedAccessCache = (
allowedPaths = [],
allowedPerms = [],
) => {
- localStorage.setItem("allowedMenuKeys", JSON.stringify(allowedMenuKeys));
- localStorage.setItem("allowedPaths", JSON.stringify(allowedPaths));
- localStorage.setItem("allowedPerms", JSON.stringify(allowedPerms));
+ setStoredSessionItem(
+ SESSION_STORAGE_KEYS.ALLOWED_MENU_KEYS,
+ JSON.stringify(allowedMenuKeys),
+ );
+ setStoredSessionItem(
+ SESSION_STORAGE_KEYS.ALLOWED_PATHS,
+ JSON.stringify(allowedPaths),
+ );
+ setStoredSessionItem(
+ SESSION_STORAGE_KEYS.ALLOWED_PERMS,
+ JSON.stringify(allowedPerms),
+ );
const currentToken = getStoredToken();
const currentTokenCacheKey = getStoredTokenCacheKey();
@@ -493,15 +838,21 @@ const persistAllowedAccessCache = (
const tokenExpireAt = Number(tokenPayload?.exp);
if (Number.isFinite(tokenIssuedAt)) {
- localStorage.setItem("allowedPathsIssuedAt", String(tokenIssuedAt));
+ setStoredSessionItem(
+ SESSION_STORAGE_KEYS.ALLOWED_PATHS_ISSUED_AT,
+ String(tokenIssuedAt),
+ );
} else {
- localStorage.removeItem("allowedPathsIssuedAt");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS_ISSUED_AT);
}
if (Number.isFinite(tokenExpireAt)) {
- localStorage.setItem("allowedPathsExpire", String(tokenExpireAt));
+ setStoredSessionItem(
+ SESSION_STORAGE_KEYS.ALLOWED_PATHS_EXPIRE,
+ String(tokenExpireAt),
+ );
} else {
- localStorage.removeItem("allowedPathsExpire");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS_EXPIRE);
}
allowedPermissionCodes.value = allowedPerms;
@@ -520,6 +871,303 @@ const isPermissionDeniedError = (error) => {
return /no\s*permission|forbidden|denied|无权限|缺少权限/i.test(message);
};
+const normalizeText = (value) => {
+ if (value === null || value === undefined) {
+ return "";
+ }
+
+ return String(value).trim();
+};
+
+const uniqueText = (values = []) =>
+ Array.from(
+ new Set(
+ values
+ .map((value) => normalizeText(value))
+ .filter((value) => value.length > 0),
+ ),
+ );
+
+const isEmail = (value) =>
+ typeof value === "string" && value.includes("@") && value.includes(".");
+
+const getCurrentShift = () =>
+ new Date().getHours() < 12 ? "morning" : "evening";
+
+const toBooleanFlag = (value) => {
+ if (typeof value === "boolean") {
+ return value;
+ }
+
+ if (typeof value === "number") {
+ return value > 0;
+ }
+
+ if (typeof value === "string") {
+ const normalized = value.trim().toLowerCase();
+ if (!normalized) {
+ return false;
+ }
+
+ return !["0", "false", "no", "none", "null", "undefined"].includes(
+ normalized,
+ );
+ }
+
+ return Boolean(value);
+};
+
+const resolveCurrentEmployeeId = async () => {
+ try {
+ const currentProfile = await fetchCurrentAccountProfile("employee");
+ const currentProfileEmployeeId = normalizeText(
+ currentProfile?.profile?.[EmployeeFields.NUMBER] ||
+ currentProfile?.userNumber,
+ );
+
+ if (currentProfileEmployeeId && !isEmail(currentProfileEmployeeId)) {
+ return currentProfileEmployeeId;
+ }
+ } catch {}
+
+ const token = getStoredToken();
+ const payload = decodeJwtPayload(token) || {};
+ const tokenIdentity = getUserIdentity("employee", token);
+ const storedAccount = normalizeText(
+ getStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT),
+ );
+
+ const identityCandidates = uniqueText([
+ payload?.EmployeeId,
+ payload?.employeeId,
+ payload?.UserNumber,
+ payload?.userNumber,
+ tokenIdentity,
+ ]);
+
+ const directEmployeeId = identityCandidates.find((value) => !isEmail(value));
+ if (directEmployeeId) {
+ return directEmployeeId;
+ }
+
+ const email = uniqueText([
+ ...identityCandidates.filter((value) => isEmail(value)),
+ isEmail(storedAccount) ? storedAccount : "",
+ ])[0];
+ if (!email) {
+ return "";
+ }
+
+ const employeeListResult = await fetchEmployees({
+ [BaseFields.PAGE]: 1,
+ [BaseFields.PAGE_SIZE]: 1,
+ [BaseFields.IS_DELETED]: 0,
+ [EmployeeFields.EMAILADDRESS]: email,
+ });
+
+ const firstEmployee = Array.isArray(employeeListResult?.Items)
+ ? employeeListResult.Items[0]
+ : null;
+ return normalizeText(firstEmployee?.[EmployeeFields.NUMBER]);
+};
+
+const resolveEmployeeCheckInSnapshot = (payload) => {
+ const currentShift = getCurrentShift();
+ const isMorningShift = currentShift === "morning";
+ const source =
+ payload &&
+ typeof payload === "object" &&
+ payload.Data &&
+ !Array.isArray(payload)
+ ? payload.Data
+ : payload;
+
+ const morningChecked = toBooleanFlag(source?.MorningChecked);
+ const eveningChecked = toBooleanFlag(source?.EveningChecked);
+
+ if (
+ source &&
+ typeof source === "object" &&
+ ("MorningChecked" in source || "EveningChecked" in source)
+ ) {
+ return {
+ shift: currentShift,
+ checked: isMorningShift ? morningChecked : eveningChecked,
+ checkDay:
+ source?.CheckDay ??
+ source?.checkDay ??
+ source?.CheckDays ??
+ source?.checkDays ??
+ "",
+ };
+ }
+
+ let checked = false;
+ if (Array.isArray(source)) {
+ checked = source.length > 0;
+ } else if (source && typeof source === "object") {
+ const explicitFlags = [
+ source.IsCheckedIn,
+ source.CheckedIn,
+ source.HasCheckedIn,
+ source.HasRecord,
+ source.Exists,
+ source.TodayCheckedIn,
+ source.TodayHasCheckedIn,
+ source.Count,
+ source.Total,
+ source.TodayCount,
+ ];
+
+ checked = explicitFlags.some((flag) =>
+ Array.isArray(flag) ? flag.length > 0 : toBooleanFlag(flag),
+ );
+
+ if (!checked) {
+ checked = Object.keys(source).length > 0;
+ }
+ } else {
+ checked = toBooleanFlag(source);
+ }
+
+ return {
+ shift: currentShift,
+ checked,
+ checkDay: "",
+ };
+};
+
+const isEmployeeCheckInRequestSuccess = (payload) => {
+ if (isApiSuccess(payload)) {
+ return true;
+ }
+
+ const statusCode = Number(
+ payload?.StatusCode ?? payload?.statusCode ?? payload?.status ?? NaN,
+ );
+ return statusCode === 200;
+};
+
+const resetEmployeeCheckInState = () => {
+ employeeCheckInStatusRequestId.value += 1;
+ employeeCheckInEmployeeId.value = "";
+ employeeCheckInStatusLoading.value = false;
+ employeeCheckInSubmitting.value = false;
+ employeeCheckedInToday.value = false;
+ employeeCurrentShift.value = getCurrentShift();
+ employeeCheckInDayCount.value = "";
+};
+
+const loadEmployeeCheckInStatus = async () => {
+ if (!showEmployeeCheckInEntry.value) {
+ resetEmployeeCheckInState();
+ return;
+ }
+
+ const requestId = employeeCheckInStatusRequestId.value + 1;
+ employeeCheckInStatusRequestId.value = requestId;
+ employeeCheckInStatusLoading.value = true;
+ employeeCheckInEmployeeId.value = "";
+ employeeCheckedInToday.value = false;
+
+ try {
+ const employeeId = await resolveCurrentEmployeeId();
+ if (requestId !== employeeCheckInStatusRequestId.value) {
+ return;
+ }
+
+ employeeCheckInEmployeeId.value = employeeId;
+ if (!employeeId) {
+ return;
+ }
+
+ const todayCheckIn = await fetchEmployeeTodayCheckIn({
+ [EmployeeFields.NUMBER]: employeeId,
+ });
+
+ if (requestId !== employeeCheckInStatusRequestId.value) {
+ return;
+ }
+
+ const checkInSnapshot = resolveEmployeeCheckInSnapshot(todayCheckIn);
+ employeeCurrentShift.value = checkInSnapshot.shift;
+ employeeCheckedInToday.value = checkInSnapshot.checked;
+ employeeCheckInDayCount.value = normalizeText(checkInSnapshot.checkDay);
+ } catch (error) {
+ } finally {
+ if (requestId === employeeCheckInStatusRequestId.value) {
+ employeeCheckInStatusLoading.value = false;
+ }
+ }
+};
+
+const handleEmployeeCheckIn = async () => {
+ if (!showEmployeeCheckInEntry.value || employeeCheckedInToday.value) {
+ return;
+ }
+
+ if (!employeeCheckInEmployeeId.value) {
+ await loadEmployeeCheckInStatus();
+ }
+
+ const employeeId = employeeCheckInEmployeeId.value;
+ if (!employeeId) {
+ showErrorNotification(t("message.employeeCheckInIdentityMissing"));
+ return;
+ }
+
+ employeeCheckInSubmitting.value = true;
+ try {
+ const currentShift = getCurrentShift();
+ const payload = {
+ [EmployeeCheckFields.EMPLOYEENUMBER]: employeeId,
+ };
+
+ employeeCurrentShift.value = currentShift;
+
+ let response = null;
+ let lastFailure = null;
+
+ try {
+ response = await addEmployeeCheckIn(payload);
+ if (!isEmployeeCheckInRequestSuccess(response)) {
+ lastFailure = response;
+ }
+ } catch (error) {
+ lastFailure = error;
+ }
+
+ if (!isEmployeeCheckInRequestSuccess(response)) {
+ try {
+ const todayCheckIn = await fetchEmployeeTodayCheckIn({
+ WorkerNo: employeeId,
+ [EmployeeFields.NUMBER]: employeeId,
+ });
+ const checkInSnapshot = resolveEmployeeCheckInSnapshot(todayCheckIn);
+ employeeCurrentShift.value = checkInSnapshot.shift;
+ employeeCheckedInToday.value = checkInSnapshot.checked;
+ employeeCheckInDayCount.value = normalizeText(checkInSnapshot.checkDay);
+ } catch {}
+
+ if (employeeCheckedInToday.value) {
+ showWarningNotification(t("message.employeeCheckInAlreadyDone"));
+ return;
+ }
+
+ showErrorNotification(getApiMessage(lastFailure || response));
+ return;
+ }
+
+ employeeCheckedInToday.value = true;
+ await loadEmployeeCheckInStatus();
+ showSuccessNotification(t("message.employeeCheckInSuccess"));
+ } catch (error) {
+ showErrorNotification(getApiMessage(error));
+ } finally {
+ employeeCheckInSubmitting.value = false;
+ }
+};
+
const getPermissionErrorMessage = () => {
const translated = t("message.permissionCheckError");
return translated === "message.permissionCheckError"
@@ -529,8 +1177,8 @@ const getPermissionErrorMessage = () => {
const getLocalIdentityFallback = () => {
const candidates = [
- localStorage.getItem("account"),
- localStorage.getItem("username"),
+ getStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT),
+ getStoredSessionItem(SESSION_STORAGE_KEYS.USERNAME),
];
for (const candidate of candidates) {
@@ -543,6 +1191,23 @@ const getLocalIdentityFallback = () => {
return "";
};
+const resolveCurrentPermissionIdentity = async () => {
+ if (loginType.value !== "customer") {
+ try {
+ const currentProfile = await fetchCurrentAccountProfile(loginType.value);
+ const userNumber = normalizeText(currentProfile?.userNumber);
+ if (userNumber) {
+ return userNumber;
+ }
+ } catch {}
+ }
+
+ return (
+ getUserIdentity(loginType.value, getStoredToken()) ||
+ getLocalIdentityFallback()
+ );
+};
+
const fetchCurrentUserPermissionCodes = async () => {
const permissionReadersByLoginType = {
admin: {
@@ -563,9 +1228,7 @@ const fetchCurrentUserPermissionCodes = async () => {
permissionReadersByLoginType[loginType.value] ||
permissionReadersByLoginType.admin;
- const identity =
- getUserIdentity(loginType.value, getStoredToken()) ||
- getLocalIdentityFallback();
+ const identity = await resolveCurrentPermissionIdentity();
if (!identity) {
return { ok: false, reason: "identity_missing", codes: [] };
@@ -588,11 +1251,6 @@ const fetchCurrentUserPermissionCodes = async () => {
? "permission_denied"
: "request_failed";
- if (import.meta.env.DEV && reason === "request_failed") {
- // eslint-disable-next-line no-console
- console.warn("[Permissions] Failed to sync permission codes:", error);
- }
-
return { ok: false, reason, codes: [] };
}
};
@@ -617,27 +1275,26 @@ const selectedKeys = computed(() => {
const normalize = (p) => (p || "").replace(/\/$/, "");
const target = normalize(route.path);
- const flatten = (list, acc = []) => {
- for (const it of list || []) {
- acc.push(it);
- if (Array.isArray(it.children) && it.children.length) {
- flatten(it.children, acc);
- }
- }
- return acc;
- };
- const items = flatten(menuData.value, []);
+ const items = flattenMenuItems(displayMenuData.value, []);
// 优先:按 path 精确匹配菜单项
- const byPath = items.find((it) => normalize(it.path) === target);
- if (byPath) {
- return [normalize(byPath.path)];
+ const byPathKeys = items
+ .filter((it) => normalize(it.path) === target)
+ .map((it) => it.menuKey || normalize(it.path))
+ .filter(Boolean);
+ if (byPathKeys.length > 0) {
+ return [...new Set(byPathKeys)];
}
// 次优:按路由名与菜单 key 精确匹配
- const byKey = route.name ? items.find((it) => it.key === route.name) : null;
- if (byKey) {
- return [normalize(byKey.path) || byKey.key];
+ const byKeyKeys = route.name
+ ? items
+ .filter((it) => it.key === route.name)
+ .map((it) => it.menuKey || normalize(it.path) || it.key)
+ .filter(Boolean)
+ : [];
+ if (byKeyKeys.length > 0) {
+ return [...new Set(byKeyKeys)];
}
// 回退:使用当前路由路径
@@ -646,9 +1303,11 @@ const selectedKeys = computed(() => {
const refreshMenu = async () => {
try {
- const menuItems = await fetchMenusTree({
- [BaseFields.USER_TOKEN]: getStoredToken(),
- });
+ // 使用新的统一接口获取用户资料、菜单树和权限编码
+ const userProfile = await fetchUserProfile();
+ const menuItems = userProfile.menus || [];
+ const permissionCodes = userProfile.permissionCodes || [];
+
const currentOpenKeys = [...openKeys.value];
const processMenuItems = (items) => {
@@ -667,7 +1326,8 @@ const refreshMenu = async () => {
};
// 1) 构建菜单渲染数据
- menuData.value = processMenuItems(menuItems.Items);
+ menuData.value = processMenuItems(menuItems);
+ syncFavoriteRoutesFromStorage();
// 2) 基于后端过滤后的菜单树,收集允许访问的路径/菜单Key与按钮权限编码,用于路由守卫与按钮级控制
const collect = (items, keys = [], paths = [], perms = []) => {
@@ -685,15 +1345,13 @@ const refreshMenu = async () => {
const allowedMenuKeys = unique(keys);
const allowedPaths = unique(paths).map((p) => (p || "").replace(/\/$/, ""));
const allowedPermListFromMenu = unique(perms);
- const syncedPermissionCodes = await fetchCurrentUserPermissionCodes();
- const allowedPermList = syncedPermissionCodes.ok
- ? syncedPermissionCodes.codes
- : allowedPermListFromMenu;
+
+ // 使用新接口返回的权限编码,如果为空则使用菜单中的权限
+ const allowedPermList =
+ permissionCodes.length > 0 ? permissionCodes : allowedPermListFromMenu;
const shouldShowPermissionWarning =
- !syncedPermissionCodes.ok &&
- syncedPermissionCodes.reason === "request_failed" &&
- allowedPermList.length === 0;
+ permissionCodes.length === 0 && allowedPermListFromMenu.length === 0;
if (shouldShowPermissionWarning) {
showWarningNotification(getPermissionErrorMessage());
@@ -733,7 +1391,7 @@ const eventHandler = () => {
watchEffect(() => {
const activeKeys =
findActiveKeys(
- menuData.value,
+ displayMenuData.value,
route.path,
route.name,
route.matched.map((r) => r.path),
@@ -747,6 +1405,109 @@ const handleLanguageChange = (value) => {
currentLocale.value = value;
};
+const syncFavoriteCollectionSilently = async (
+ reason,
+ { showGeneric = false } = {},
+) => {
+ try {
+ await syncFavoriteCollection({ reason, transport: "axios" });
+ syncFavoriteRoutesFromStorage();
+ return true;
+ } catch (error) {
+ handleFavoriteCollectionSyncError(error, { showGeneric });
+ return false;
+ }
+};
+
+const handleFavoriteCollectionSyncError = (
+ error,
+ { showGeneric = false } = {},
+) => {
+ syncFavoriteRoutesFromStorage();
+
+ const code = Number(error?.Code || error?.code || error?.payload?.Code || 0);
+ if (code === ERROR_CODES.AUTH_PERMISSION_DENIED) {
+ showWarningNotification(t("message.favoriteCollectionIdentityChanged"));
+ return;
+ }
+
+ if (code === ERROR_CODES.CONCURRENCY_CONFLICT) {
+ showWarningNotification(t("message.favoriteCollectionConflictRefreshed"));
+ return;
+ }
+
+ if (showGeneric) {
+ showErrorNotification(
+ resolveErrorMessage(error, t("message.favoriteCollectionSyncFailed")),
+ );
+ }
+};
+
+const loadFavoriteCollectionFromServer = async () => {
+ if (!isLoggedIn.value || !getStoredToken()) {
+ return;
+ }
+
+ try {
+ await loadFavoriteCollection(getFavoriteCollectionOwner());
+ } catch (error) {
+ handleFavoriteCollectionSyncError(error);
+ } finally {
+ syncFavoriteRoutesFromStorage();
+ }
+};
+
+const toggleCurrentRouteFavorite = () => {
+ if (!showFavoriteAction.value) {
+ return;
+ }
+
+ const { favorited, routes } = toggleFavoriteRoute(
+ currentNormalizedRoutePath.value,
+ getFavoriteCollectionOwner(),
+ );
+ favoriteRoutes.value = routes;
+
+ showSuccessNotification(
+ favorited
+ ? translateFavoriteMessage(
+ "message.favoriteAdded",
+ "已加入收藏夹",
+ "Page added to favorites",
+ )
+ : translateFavoriteMessage(
+ "message.favoriteRemoved",
+ "已取消收藏",
+ "Page removed from favorites",
+ ),
+ );
+
+ favoriteToggleSyncLoading.value = true;
+ syncFavoriteCollectionSilently("manual", {
+ showGeneric: true,
+ }).finally(() => {
+ favoriteToggleSyncLoading.value = false;
+ });
+};
+
+const handleFavoriteCollectionStorageChange = (event) => {
+ if (
+ event.key === "favoriteCollection" ||
+ event.key === "favoriteCollectionOwner"
+ ) {
+ syncFavoriteRoutesFromStorage();
+ }
+};
+
+const handleFavoriteSyncPendingBeforeUnload = (event) => {
+ if (!favoriteToggleSyncLoading.value && !favoriteBlockingSyncLoading.value) {
+ return;
+ }
+
+ event.preventDefault();
+ event.returnValue = "";
+};
+
const normalizeSixDigits = (value) =>
String(value || "")
.replace(/\D/g, "")
@@ -979,13 +1740,14 @@ const checkLoginStatus = () => {
if (storedToken && !isTokenExpired(storedToken)) {
isLoggedIn.value = true;
- loginType.value = localStorage.getItem("loginType") || "admin";
+ loginType.value =
+ getStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE) || "admin";
const tokenPayload = decodeJwtPayload(storedToken);
const tokenExpireAt = Number(tokenPayload?.exp);
const cachedAllowedToken = getStoredAllowedPathsToken();
const cachedAllowedExpireAt = Number(
- localStorage.getItem("allowedPathsExpire"),
+ getStoredSessionItem(SESSION_STORAGE_KEYS.ALLOWED_PATHS_EXPIRE),
);
const now = Math.floor(Date.now() / 1000);
@@ -1010,94 +1772,262 @@ const checkLoginStatus = () => {
syncAllowedPermissions();
}
- const storedUsername = localStorage.getItem("username");
+ const storedUsername = getStoredSessionItem(SESSION_STORAGE_KEYS.USERNAME);
username.value = storedUsername || "User";
+ syncFavoriteRoutesFromStorage();
return;
}
isLoggedIn.value = false;
loginType.value = "admin";
allowedPermissionCodes.value = [];
+ favoriteRoutes.value = [];
username.value = "";
clearStoredToken();
- localStorage.removeItem("username");
- localStorage.removeItem("account");
- localStorage.removeItem("loginType");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.USERNAME);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE);
clearAllowedPathsCache();
clearRowVersionCache();
+ clearFavoriteCollectionState();
};
const logout = async () => {
- var response = await signOut();
- if (!isApiSuccess(response)) {
- showErrorNotification(response.Message || t("message.logoutFailed"));
+ favoriteBlockingSyncLoading.value = true;
+
+ try {
+ await syncFavoriteCollectionOnLogout();
+ } catch {}
+
+ try {
+ const response = await signOut();
+ if (!isApiSuccess(response)) {
+ favoriteBlockingSyncLoading.value = false;
+ showErrorNotification(response.Message || t("message.logoutFailed"));
+ return;
+ }
+ } catch (error) {
+ favoriteBlockingSyncLoading.value = false;
+ showErrorNotification(error?.Message || t("message.logoutFailed"));
return;
}
+
clearStoredToken();
- localStorage.removeItem("username");
- localStorage.removeItem("account");
- localStorage.removeItem("loginType");
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.USERNAME);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT);
+ clearStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE);
clearAllowedPathsCache();
clearRowVersionCache();
+ clearFavoriteCollectionState();
+ clearSavedTabs();
isLoggedIn.value = false;
allowedPermissionCodes.value = [];
+ favoriteRoutes.value = [];
username.value = "";
closeTwoFactorModal();
+ favoriteBlockingSyncLoading.value = false;
router.push("/signin");
};
+const openProfileCenter = () => {
+ if (!isLoggedIn.value) {
+ return;
+ }
+
+ router.push("/profile");
+};
+
onBeforeMount(() => {
currentLocale.value = locale.value;
checkLoginStatus();
});
+watch(
+ [isLoggedIn, loginType],
+ ([loggedIn, currentLoginType]) => {
+ if (loggedIn && currentLoginType === "employee") {
+ loadEmployeeCheckInStatus();
+ return;
+ }
+
+ resetEmployeeCheckInState();
+ },
+ { immediate: true },
+);
+
onMounted(async () => {
try {
await refreshMenu();
- await fetchServerVersion();
+ await Promise.all([
+ loadFavoriteCollectionFromServer(),
+ fetchServerVersion(),
+ ]);
} catch (error) {
showErrorNotification(t("message.fetchMenuError"));
}
+ bindFavoriteCollectionAutoSync();
+ window.addEventListener("storage", handleFavoriteCollectionStorageChange);
+ window.addEventListener(
+ "beforeunload",
+ handleFavoriteSyncPendingBeforeUnload,
+ );
emitter.on("refresh-menu", eventHandler);
});
onUnmounted(() => {
+ unbindFavoriteCollectionAutoSync();
+ window.removeEventListener("storage", handleFavoriteCollectionStorageChange);
+ window.removeEventListener(
+ "beforeunload",
+ handleFavoriteSyncPendingBeforeUnload,
+ );
emitter.off("refresh-menu", eventHandler);
});
+const verticalOpenKeys = ref([]);
+
const handleOpenChange = (keys) => {
- openKeys.value = keys;
+ if (siderCollapsed.value) {
+ verticalOpenKeys.value = keys;
+ } else {
+ openKeys.value = keys;
+ }
+};
+
+const handleCollapsedUpdate = (collapsed) => {
+ siderCollapsed.value = collapsed;
+};
+
+watch(siderCollapsed, (val) => {
+ if (val) {
+ verticalOpenKeys.value = [];
+ }
+});
+
+const toggleSider = () => {
+ siderCollapsed.value = !siderCollapsed.value;
+};
+
+const handleBreakpoint = (broken) => {
+ if (broken) {
+ siderCollapsed.value = true;
+ }
};
watch(
() => route.path,
async (newPath) => {
await nextTick();
- // 避免在菜单数据尚未加载完成时去展开子菜单,防止运行时对不存在的 DOM 节点进行操作
- if (!Array.isArray(menuData.value) || menuData.value.length === 0) {
+ if (
+ !Array.isArray(displayMenuData.value) ||
+ displayMenuData.value.length === 0
+ ) {
return;
}
const activeKeys =
findActiveKeys(
- menuData.value,
+ displayMenuData.value,
newPath,
route.name,
route.matched.map((r) => r.path),
) || [];
openKeys.value = [...new Set([...openKeys.value, ...activeKeys])];
checkLoginStatus();
+
+ const normalizedPath = (newPath || "").replace(/\/$/, "");
+ if (normalizedPath === "/signin" || normalizedPath === "") {
+ return;
+ }
+ const closable = normalizedPath !== "/home";
+ const title = getPageTitle(normalizedPath);
+ tabsContext.addTab({
+ path: normalizedPath,
+ title: title || undefined,
+ name: route.name,
+ closable,
+ });
},
{ immediate: true },
);
diff --git a/src/layouts/Menu/RecursiveMenu.vue b/src/layouts/Menu/RecursiveMenu.vue
index 86fdc56a637930b3568f8f524329457e5ac4a184..8b4daf33f1cfb9dd391e9201a54a28573790d364 100644
--- a/src/layouts/Menu/RecursiveMenu.vue
+++ b/src/layouts/Menu/RecursiveMenu.vue
@@ -1,13 +1,13 @@
-
+
@@ -18,7 +18,7 @@
-
+
@@ -70,6 +70,10 @@ const handleMenuClick = (e, navigate) => {
const hasChildren = computed(() => {
return !!props.item.children?.length;
});
+
+const menuItemKey = computed(
+ () => props.item.menuKey || props.item.path || props.item.key,
+);
diff --git a/src/views/base/DepartmentView.vue b/src/views/base/DepartmentView.vue
index ec097915e7a06327bcfcd61f948a1fa6317539c4..923f1fa49721271cdc84b800907ed4fad2fe2cf0 100644
--- a/src/views/base/DepartmentView.vue
+++ b/src/views/base/DepartmentView.vue
@@ -5,6 +5,38 @@
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -126,13 +161,19 @@
{{ form[DepartmentFields.NUMBER] }}
-
+
-
+
{{ formatDate(selectedRecord?.[DepartmentFields.CREATIONDATE]) }}
+
+
+ {{ selectedRecord?.[DepartmentFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[DepartmentFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[DepartmentFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[DepartmentFields.DATA_CHG_DATE])
+ }}
+
+
@@ -202,32 +263,37 @@ import {
addDepartment,
updateDepartment,
deleteDepartment,
-} from "@/api/departmentapi";
+} from "@/api/base/departmentapi";
import {
DepartmentFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/department.entity";
-import { EmployeeFields } from "@/entities/employee.entity";
-import { fetchEmployees } from "@/api/employeeapi";
+} from "@/entities/base/department.entity";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
+import { BaseFields } from "@/entities/common.entity";
+import { fetchEmployees } from "@/api/humanresourcemanagement/employeeapi";
import {
formatDate,
+ formatDateTime,
showErrorNotification,
showSuccessNotification,
} from "@/utils/index";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import generateUniqueId from "@/utils/uniquecode";
import dayjs from "dayjs";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getDepartmentFilterConfig } from "@/filters/department.filter.config";
+import { getDepartmentFilterConfig } from "@/filters/base/department.filter.config";
import { DepartmentPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const departments = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -254,7 +320,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[DepartmentFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -277,7 +343,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[DepartmentFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -377,7 +443,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...departmentFilterConfig.value?.fields.map((f) => f.name),
- DepartmentFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -392,7 +458,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === DepartmentFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -459,12 +525,22 @@ const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const isFormValid = computed(() => {
+ return !!(
+ form[DepartmentFields.NAME]?.trim() && form[DepartmentFields.LEADER]
+ );
+});
+
const departmentNoLabel = computed(() => t("message.departmentNo"));
const departmentNameLabel = computed(() => t("message.departmentName"));
const departmentDescLabel = computed(() => t("message.departmentDesc"));
const departmentLeaderLabel = computed(() => t("message.departmentLeader"));
const departmentParentLabel = computed(() => t("message.departmentParent"));
const departmentDateLabel = computed(() => t("message.departmentDate"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -475,13 +551,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchDepartmentData = async () => {
+const fetchDepartmentData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchDepartments({
- [DepartmentFields.PAGE]: pagination.current,
- [DepartmentFields.PAGE_SIZE]: pagination.pageSize,
- [DepartmentFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result && Array.isArray(result.Items)) {
@@ -496,7 +578,11 @@ const fetchDepartmentData = async () => {
[DepartmentFields.PARENT]: item[DepartmentFields.PARENT],
[DepartmentFields.PARENTNAME]: item[DepartmentFields.PARENTNAME],
[DepartmentFields.CREATIONDATE]: item[DepartmentFields.CREATIONDATE],
- [DepartmentFields.IS_DELETED]: item[DepartmentFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [DepartmentFields.DATA_INS_USER]: item[DepartmentFields.DATA_INS_USER],
+ [DepartmentFields.DATA_INS_DATE]: item[DepartmentFields.DATA_INS_DATE],
+ [DepartmentFields.DATA_CHG_USER]: item[DepartmentFields.DATA_CHG_USER],
+ [DepartmentFields.DATA_CHG_DATE]: item[DepartmentFields.DATA_CHG_DATE],
[DepartmentFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -513,8 +599,8 @@ const fetchDepartmentData = async () => {
const fetchSelectDepartments = async () => {
try {
const result = await fetchDepartments({
- [DepartmentFields.IS_DELETED]: 0,
- [DepartmentFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
departmentOptions.value = result.Items.map((item) => ({
label: item[DepartmentFields.NAME],
@@ -528,8 +614,8 @@ const fetchSelectDepartments = async () => {
const fetchSelectLeaders = async () => {
try {
const result = await fetchEmployees({
- [EmployeeFields.IS_DELETED]: 0,
- [EmployeeFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
leaderOptions.value = result.Items.map((item) => ({
label: item[EmployeeFields.NAME],
@@ -546,11 +632,17 @@ onMounted(() => {
fetchSelectLeaders();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertDepartment");
form[DepartmentFields.ID] = 0;
- form[DepartmentFields.NUMBER] = generateSnowflakeId({
+ form[DepartmentFields.NUMBER] = generateUniqueId({
prefix: "D-",
separator: null,
});
@@ -562,6 +654,57 @@ const showModal = () => {
form[DepartmentFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: departments.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = departments.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchDepartmentData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: departments.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ departments.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchDepartmentData();
@@ -576,7 +719,7 @@ const editDepartment = (record) => {
form[DepartmentFields.DESCRIPTION] = record[DepartmentFields.DESCRIPTION];
form[DepartmentFields.LEADER] = record[DepartmentFields.LEADER];
form[DepartmentFields.PARENT] = record[DepartmentFields.PARENT];
- form[DepartmentFields.IS_DELETED] = record[DepartmentFields.IS_DELETED];
+ form[BaseFields.IS_DELETED] = record[BaseFields.IS_DELETED];
form[DepartmentFields.CREATIONDATE] = record[DepartmentFields.CREATIONDATE]
? dayjs(record[DepartmentFields.CREATIONDATE])
: null;
@@ -616,7 +759,8 @@ const handleModalOk = async () => {
clearSelection(); // 重置选择
fetchDepartmentData();
} catch (error) {
- showErrorNotification(t("message.pleaseTryAgainLater"));
+ // 表单验证失败,不显示错误提示
+ return;
} finally {
confirmLoading.value = false;
}
@@ -628,7 +772,7 @@ const handleModalCancel = () => {
const restoreDepartment = async (record) => {
try {
- record[DepartmentFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateDepartment(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
@@ -652,10 +796,3 @@ const handleSorterChange = (pagination, filters, sorter) => {
sortedInfo.value = sorter;
};
-
-
diff --git a/src/views/base/NationView.vue b/src/views/base/NationView.vue
index db7009eb44e623c35644876d2ecfded92e47a5a6..238ca578638018d44054fe2dbfdee96dacaee575 100644
--- a/src/views/base/NationView.vue
+++ b/src/views/base/NationView.vue
@@ -5,6 +5,38 @@
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -121,7 +156,10 @@
{{ form[NationFields.NUMBER] }}
-
+
@@ -139,6 +177,22 @@
{{ selectedRecord?.[NationFields.NAME] }}
+
+
+ {{ selectedRecord?.[NationFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[NationFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[NationFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[NationFields.DATA_CHG_DATE]) }}
+
+
@@ -148,30 +202,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchNations,
addNation,
updateNation,
deleteNation,
-} from "@/api/nationapi";
+} from "@/api/base/nationapi";
import {
NationFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/nation.entity";
+} from "@/entities/base/nation.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getNationFilterConfig } from "@/filters/nation.filter.config";
+import { getNationFilterConfig } from "@/filters/base/nation.filter.config";
import { NationPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const nations = ref([]);
const viewModalVisible = ref(false);
const modalVisible = ref(false);
@@ -191,7 +254,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[NationFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -214,7 +277,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[NationFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -347,9 +410,14 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const nationNoLabel = computed(() => t("message.nationNo"));
const nationNameLabel = computed(() => t("message.nationName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -360,13 +428,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchNationData = async () => {
+const fetchNationData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchNations({
- [NationFields.PAGE]: pagination.current,
- [NationFields.PAGE_SIZE]: pagination.pageSize,
- [NationFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -375,7 +449,11 @@ const fetchNationData = async () => {
[NationFields.ROWVERSION]: item[NationFields.ROWVERSION],
[NationFields.NUMBER]: item[NationFields.NUMBER],
[NationFields.NAME]: item[NationFields.NAME],
- [NationFields.IS_DELETED]: item[NationFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [NationFields.DATA_INS_USER]: item[NationFields.DATA_INS_USER],
+ [NationFields.DATA_INS_DATE]: item[NationFields.DATA_INS_DATE],
+ [NationFields.DATA_CHG_USER]: item[NationFields.DATA_CHG_USER],
+ [NationFields.DATA_CHG_DATE]: item[NationFields.DATA_CHG_DATE],
[NationFields.SELECTED]: false,
}));
pagination.total = result.TotlalCount;
@@ -393,10 +471,16 @@ onMounted(() => {
fetchNationData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertNation");
- form[NationFields.NUMBER] = generateSnowflakeId({
+ form[NationFields.NUMBER] = generateUniqueId({
prefix: "N-",
separator: null,
});
@@ -404,6 +488,57 @@ const showModal = () => {
form[NationFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: nations.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = nations.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchNationData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: nations.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ nations.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchNationData();
@@ -453,7 +588,7 @@ const handleModalCancel = () => {
const restoreNation = async (record) => {
try {
- record[NationFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateNation(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/base/NoticeTypeView.vue b/src/views/base/NoticeTypeView.vue
index 28bef0aeccfc88e979227672d34f070abebfb3e0..bd04428d0c2b45085d9cc90e13dc74936a41b85f 100644
--- a/src/views/base/NoticeTypeView.vue
+++ b/src/views/base/NoticeTypeView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -123,7 +158,10 @@
{{ form[NoticeTypeFields.NUMBER] }}
-
+
@@ -141,6 +179,26 @@
{{ selectedRecord?.[NoticeTypeFields.NAME] }}
+
+
+ {{ selectedRecord?.[NoticeTypeFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[NoticeTypeFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[NoticeTypeFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[NoticeTypeFields.DATA_CHG_DATE])
+ }}
+
+
@@ -150,30 +208,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchNoticeTypes,
addNoticeType,
updateNoticeType,
deleteNoticeType,
-} from "@/api/noticetypeapi";
+} from "@/api/base/noticetypeapi";
import {
NoticeTypeFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/noticetype.entity";
+} from "@/entities/base/noticetype.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getNoticeTypeFilterConfig } from "@/filters/noticetype.filter.config";
+import { getNoticeTypeFilterConfig } from "@/filters/base/noticetype.filter.config";
import { NoticeTypePermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const notices = ref([]);
const viewModalVisible = ref(false);
const modalVisible = ref(false);
@@ -193,7 +260,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[NoticeTypeFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -216,7 +283,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[NoticeTypeFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -351,9 +418,14 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const noticeTypeNoLabel = computed(() => t("message.noticeTypeNumber"));
const noticeTypeNameLabel = computed(() => t("message.noticeTypeName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -364,13 +436,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchNoticeTypeData = async () => {
+const fetchNoticeTypeData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchNoticeTypes({
- [NoticeTypeFields.PAGE]: pagination.current,
- [NoticeTypeFields.PAGE_SIZE]: pagination.pageSize,
- [NoticeTypeFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -379,7 +457,11 @@ const fetchNoticeTypeData = async () => {
[NoticeTypeFields.ROWVERSION]: item[NoticeTypeFields.ROWVERSION],
[NoticeTypeFields.NUMBER]: item[NoticeTypeFields.NUMBER],
[NoticeTypeFields.NAME]: item[NoticeTypeFields.NAME],
- [NoticeTypeFields.IS_DELETED]: item[NoticeTypeFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [NoticeTypeFields.DATA_INS_USER]: item[NoticeTypeFields.DATA_INS_USER],
+ [NoticeTypeFields.DATA_INS_DATE]: item[NoticeTypeFields.DATA_INS_DATE],
+ [NoticeTypeFields.DATA_CHG_USER]: item[NoticeTypeFields.DATA_CHG_USER],
+ [NoticeTypeFields.DATA_CHG_DATE]: item[NoticeTypeFields.DATA_CHG_DATE],
[NoticeTypeFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -396,10 +478,16 @@ onMounted(() => {
fetchNoticeTypeData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertNoticeType");
- form[NoticeTypeFields.NUMBER] = generateSnowflakeId({
+ form[NoticeTypeFields.NUMBER] = generateUniqueId({
prefix: "NT-",
separator: null,
});
@@ -407,6 +495,57 @@ const showModal = () => {
form[NoticeTypeFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: notices.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = notices.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchNoticeTypeData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: notices.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ notices.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchNoticeTypeData();
@@ -456,7 +595,7 @@ const handleModalCancel = () => {
const restoreNoticeType = async (record) => {
try {
- record[NoticeTypeFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateNoticeType(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/base/PassportView.vue b/src/views/base/PassportView.vue
index 380757420043eaef50c926249679babda8365c8c..1c9be5e5759d77186356ddc59922f80034e75b6a 100644
--- a/src/views/base/PassportView.vue
+++ b/src/views/base/PassportView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -118,11 +153,14 @@
-
+
@@ -140,6 +178,22 @@
{{ selectedRecord?.[PassportFields.NAME] }}
+
+
+ {{ selectedRecord?.[PassportFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[PassportFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[PassportFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[PassportFields.DATA_CHG_DATE]) }}
+
+
@@ -149,29 +203,38 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchPassports,
addPassport,
updatePassport,
deletePassport,
-} from "@/api/passportapi";
+} from "@/api/base/passportapi";
import {
PassportFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/passport.entity";
+} from "@/entities/base/passport.entity";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getPassportFilterConfig } from "@/filters/passport.filter.config";
+import { getPassportFilterConfig } from "@/filters/base/passport.filter.config";
import { PassportPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const passports = ref([]);
const viewModalVisible = ref(false);
const modalVisible = ref(false);
@@ -191,7 +254,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[PassportFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -214,7 +277,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[PassportFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -345,9 +408,14 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const passportNoLabel = computed(() => t("message.passportNo"));
const passportNameLabel = computed(() => t("message.passportName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -358,13 +426,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchPassportData = async () => {
+const fetchPassportData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchPassports({
- [PassportFields.PAGE]: pagination.current,
- [PassportFields.PAGE_SIZE]: pagination.pageSize,
- [PassportFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -373,7 +447,11 @@ const fetchPassportData = async () => {
[PassportFields.ROWVERSION]: item[PassportFields.ROWVERSION],
[PassportFields.NUMBER]: item[PassportFields.NUMBER],
[PassportFields.NAME]: item[PassportFields.NAME],
- [PassportFields.IS_DELETED]: item[PassportFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [PassportFields.DATA_INS_USER]: item[PassportFields.DATA_INS_USER],
+ [PassportFields.DATA_INS_DATE]: item[PassportFields.DATA_INS_DATE],
+ [PassportFields.DATA_CHG_USER]: item[PassportFields.DATA_CHG_USER],
+ [PassportFields.DATA_CHG_DATE]: item[PassportFields.DATA_CHG_DATE],
[PassportFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -390,7 +468,13 @@ onMounted(() => {
fetchPassportData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertPassport");
form[PassportFields.NUMBER] = null;
@@ -398,6 +482,57 @@ const showModal = () => {
form[PassportFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: passports.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = passports.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchPassportData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: passports.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ passports.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchPassportData();
@@ -447,7 +582,7 @@ const handleModalCancel = () => {
const restorePassport = async (record) => {
try {
- record[PassportFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updatePassport(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/base/PositionView.vue b/src/views/base/PositionView.vue
index 59c6a6b92e3e3c72c29c48b179b407d2d27a5691..585a8ac5332b49a2f98749a42be303ab4566c7b8 100644
--- a/src/views/base/PositionView.vue
+++ b/src/views/base/PositionView.vue
@@ -5,6 +5,38 @@
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -120,7 +155,10 @@
{{ form[PositionFields.NUMBER] }}
-
+
@@ -138,6 +176,22 @@
{{ selectedRecord?.[PositionFields.NAME] }}
+
+
+ {{ selectedRecord?.[PositionFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[PositionFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[PositionFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[PositionFields.DATA_CHG_DATE]) }}
+
+
@@ -147,30 +201,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchPositions,
addPosition,
updatePosition,
deletePosition,
-} from "@/api/positionapi";
+} from "@/api/base/positionapi";
import {
PositionFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/position.entity";
+} from "@/entities/base/position.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getPositionFilterConfig } from "@/filters/position.filter.config";
+import { getPositionFilterConfig } from "@/filters/base/position.filter.config";
import { PositionPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const positions = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -190,7 +253,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[PositionFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -213,7 +276,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[PositionFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -343,9 +406,14 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const positionNoLabel = computed(() => t("message.positionNo"));
const positionNameLabel = computed(() => t("message.positionName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -356,13 +424,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchPositionData = async () => {
+const fetchPositionData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchPositions({
- [PositionFields.PAGE]: pagination.current,
- [PositionFields.PAGE_SIZE]: pagination.pageSize,
- [PositionFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result && Array.isArray(result.Items)) {
@@ -371,7 +445,11 @@ const fetchPositionData = async () => {
[PositionFields.ROWVERSION]: item[PositionFields.ROWVERSION],
[PositionFields.NUMBER]: item[PositionFields.NUMBER],
[PositionFields.NAME]: item[PositionFields.NAME],
- [PositionFields.IS_DELETED]: item[PositionFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [PositionFields.DATA_INS_USER]: item[PositionFields.DATA_INS_USER],
+ [PositionFields.DATA_INS_DATE]: item[PositionFields.DATA_INS_DATE],
+ [PositionFields.DATA_CHG_USER]: item[PositionFields.DATA_CHG_USER],
+ [PositionFields.DATA_CHG_DATE]: item[PositionFields.DATA_CHG_DATE],
[PositionFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -387,11 +465,17 @@ onMounted(() => {
fetchPositionData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertPosition");
form[PositionFields.ID] = 0;
- form[PositionFields.NUMBER] = generateSnowflakeId({
+ form[PositionFields.NUMBER] = generateUniqueId({
prefix: "P-",
separator: null,
});
@@ -399,6 +483,57 @@ const showModal = () => {
form[PositionFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: positions.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = positions.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchPositionData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: positions.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ positions.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchPositionData();
@@ -448,7 +583,7 @@ const handleModalCancel = () => {
const restorePosition = async (record) => {
try {
- record[PositionFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updatePosition(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/base/PromotionContentView.vue b/src/views/base/PromotionContentView.vue
index 605c74b8f6e4bff9ed914ca2f4e109dd2ab9f2f7..7d3a0ff6fc58fdd5be35325883ae406be57db2b0 100644
--- a/src/views/base/PromotionContentView.vue
+++ b/src/views/base/PromotionContentView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -131,6 +166,7 @@
>
@@ -151,6 +187,30 @@
{{ selectedRecord?.[PromotionContentFields.MESSAGE] }}
+
+
+ {{ selectedRecord?.[PromotionContentFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[PromotionContentFields.DATA_INS_DATE],
+ )
+ }}
+
+
+
+
+ {{ selectedRecord?.[PromotionContentFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[PromotionContentFields.DATA_CHG_DATE],
+ )
+ }}
+
+
@@ -160,30 +220,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchPromotionContents,
addPromotionContent,
updatePromotionContent,
deletePromotionContent,
-} from "@/api/promotionContentapi";
+} from "@/api/base/promotioncontentapi";
import {
PromotionContentFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/promotionContent.entity";
+} from "@/entities/base/promotioncontent.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getPromotionContentFilterConfig } from "@/filters/promotioncontent.filter.config";
+import { getPromotionContentFilterConfig } from "@/filters/base/promotioncontent.filter.config";
import { PromotionContentPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const promotionContents = ref([]);
const viewModalVisible = ref(false);
const modalVisible = ref(false);
@@ -205,7 +274,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[PromotionContentFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -228,7 +297,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[PromotionContentFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -365,6 +434,7 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const promotionContentNoLabel = computed(() =>
t("message.promotionContentNumber"),
@@ -372,6 +442,10 @@ const promotionContentNoLabel = computed(() =>
const promotionContentMessageLabel = computed(() =>
t("message.promotionContentMessage"),
);
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -382,13 +456,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchPromotionContentData = async () => {
+const fetchPromotionContentData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchPromotionContents({
- [PromotionContentFields.PAGE]: pagination.current,
- [PromotionContentFields.PAGE_SIZE]: pagination.pageSize,
- [PromotionContentFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -398,8 +478,15 @@ const fetchPromotionContentData = async () => {
item[PromotionContentFields.ROWVERSION],
[PromotionContentFields.NUMBER]: item[PromotionContentFields.NUMBER],
[PromotionContentFields.MESSAGE]: item[PromotionContentFields.MESSAGE],
- [PromotionContentFields.IS_DELETED]:
- item[PromotionContentFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [PromotionContentFields.DATA_INS_USER]:
+ item[PromotionContentFields.DATA_INS_USER],
+ [PromotionContentFields.DATA_INS_DATE]:
+ item[PromotionContentFields.DATA_INS_DATE],
+ [PromotionContentFields.DATA_CHG_USER]:
+ item[PromotionContentFields.DATA_CHG_USER],
+ [PromotionContentFields.DATA_CHG_DATE]:
+ item[PromotionContentFields.DATA_CHG_DATE],
[PromotionContentFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -416,11 +503,17 @@ onMounted(() => {
fetchPromotionContentData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertPromotionContent");
form[PromotionContentFields.ID] = 0;
- form[PromotionContentFields.NUMBER] = generateSnowflakeId({
+ form[PromotionContentFields.NUMBER] = generateUniqueId({
prefix: "PC-",
separator: null,
});
@@ -428,6 +521,57 @@ const showModal = () => {
form[PromotionContentFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: promotionContents.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = promotionContents.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchPromotionContentData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: promotionContents.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ promotionContents.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchPromotionContentData();
@@ -477,7 +621,7 @@ const handleModalCancel = () => {
const restorePromotionContent = async (record) => {
try {
- record[PromotionContentFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updatePromotionContent(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/base/QualificationView.vue b/src/views/base/QualificationView.vue
index 9333ca4d139bb65b37ac9204ea4f514f304b7a72..28df24ab8d744c203122d97c7068ee5c9499ffc5 100644
--- a/src/views/base/QualificationView.vue
+++ b/src/views/base/QualificationView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -126,7 +161,10 @@
:label="qualificationNameLabel"
:name="EducationFields.NAME"
>
-
+
@@ -144,6 +182,26 @@
{{ selectedRecord?.[EducationFields.NAME] }}
+
+
+ {{ selectedRecord?.[EducationFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[EducationFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[EducationFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[EducationFields.DATA_CHG_DATE])
+ }}
+
+
@@ -153,30 +211,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchQualifications,
addQualification,
updateQualification,
deleteQualification,
-} from "@/api/qualificationapi";
+} from "@/api/base/qualificationapi";
import {
EducationFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/qualification.entity";
+} from "@/entities/base/qualification.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getQualificationFilterConfig } from "@/filters/qualification.filter.config";
+import { getQualificationFilterConfig } from "@/filters/base/qualification.filter.config";
import { QualificationPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const qualifications = ref([]);
const viewModalVisible = ref(false);
const modalVisible = ref(false);
@@ -198,7 +265,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[EducationFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -221,7 +288,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[EducationFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -352,10 +419,15 @@ const confirmLoading = ref(false);
const formRef = ref(null);
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const sortedInfo = ref({ order: null, columnKey: null });
const qualificationNoLabel = computed(() => t("message.qualificationNo"));
const qualificationNameLabel = computed(() => t("message.qualificationName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const pagination = reactive({
current: 1,
@@ -366,13 +438,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchQualificationData = async () => {
+const fetchQualificationData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchQualifications({
- [EducationFields.PAGE]: pagination.current,
- [EducationFields.PAGE_SIZE]: pagination.pageSize,
- [EducationFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -381,7 +459,11 @@ const fetchQualificationData = async () => {
[EducationFields.ROWVERSION]: item[EducationFields.ROWVERSION],
[EducationFields.NUMBER]: item[EducationFields.NUMBER],
[EducationFields.NAME]: item[EducationFields.NAME],
- [EducationFields.IS_DELETED]: item[EducationFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [EducationFields.DATA_INS_USER]: item[EducationFields.DATA_INS_USER],
+ [EducationFields.DATA_INS_DATE]: item[EducationFields.DATA_INS_DATE],
+ [EducationFields.DATA_CHG_USER]: item[EducationFields.DATA_CHG_USER],
+ [EducationFields.DATA_CHG_DATE]: item[EducationFields.DATA_CHG_DATE],
[EducationFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -397,19 +479,76 @@ onMounted(() => {
fetchQualificationData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertQualification");
form[EducationFields.ID] = 0;
- form[EducationFields.NUMBER] = generateSnowflakeId({
+ form[EducationFields.NUMBER] = generateUniqueId({
prefix: "E-",
separator: null,
});
form[EducationFields.NAME] = "";
- form[EducationFields.IS_DELETED] = 0;
+ form[BaseFields.IS_DELETED] = 0;
form[EducationFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: qualifications.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = qualifications.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchQualificationData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: qualifications.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ qualifications.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchQualificationData();
@@ -455,7 +594,7 @@ const handleModalCancel = () => {
const restoreQualification = async (record) => {
try {
- record[EducationFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateQualification(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/customermanagement/CustomerSpendView.vue b/src/views/customermanagement/CustomerSpendView.vue
index 915b19cbac34e81744aa77e8936c4be86ab0e24b..425106b110342dfa1f0fedae98d7a34f6db016f3 100644
--- a/src/views/customermanagement/CustomerSpendView.vue
+++ b/src/views/customermanagement/CustomerSpendView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -101,8 +137,14 @@
-
-
+
+
@@ -184,8 +226,8 @@
{{ selectedRecord?.[SpendInfoFields.NUMBER] }}
-
- {{ selectedRecord?.[SpendInfoFields.ROOM_NO] }}
+
+ {{ selectedRecord?.[SpendInfoFields.ROOM_LOCATOR] || "-" }}
{{ selectedRecord?.[SpendInfoFields.CUSTO_NO] }}
@@ -205,6 +247,26 @@
{{ formatDateTime(selectedRecord?.[SpendInfoFields.TIME]) }}
+
+
+ {{ selectedRecord?.[SpendInfoFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[SpendInfoFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[SpendInfoFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[SpendInfoFields.DATA_CHG_DATE])
+ }}
+
+
@@ -214,7 +276,10 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { fetchSpendInfos, updateSpendInfo } from "@/api/spendinfoapi";
+import {
+ fetchSpendInfos,
+ updateSpendInfo,
+} from "@/api/customermanagement/spendinfoapi";
import {
SpendInfoFields,
DATE_FORMAT,
@@ -222,23 +287,28 @@ import {
getColumns,
getFormRules,
StateColors,
-} from "@/entities/spendinfo.entity";
+} from "@/entities/customermanagement/spendinfo.entity";
import {
showErrorNotification,
showSuccessNotification,
formatDateTime,
+ exportTableToCsv,
} from "@/utils/index";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import { CustomerSpendPermissions } from "@/common/permissioncode";
import dayjs from "dayjs";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getSpendInfoFilterConfig } from "@/filters/spendinfo.filter.config";
+import { getSpendInfoFilterConfig } from "@/filters/customermanagement/spendinfo.filter.config";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const spends = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -255,15 +325,20 @@ const currentFilterParams = reactive({});
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const spendNumberLabel = computed(() => t("message.spendNumber"));
-const roomNoLabel = computed(() => t("message.roomNo"));
+const roomLocatorLabel = computed(() => t("message.roomLocator"));
const customerNoLabel = computed(() => t("message.customerNo"));
const spendNameLabel = computed(() => t("message.spendName"));
const spendAmountLabel = computed(() => t("message.spendAmount"));
const spendPriceLabel = computed(() => t("message.spendPrice"));
const spendMoneyLabel = computed(() => t("message.spendMoney"));
const spendTimeLabel = computed(() => t("message.spendTime"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const spendInfoFilterConfig = computed(() => getSpendInfoFilterConfig(t));
@@ -288,10 +363,49 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[SpendInfoFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
+const selectRow = (record) => {
+ const recordId = record[SpendInfoFields.ID];
+
+ if (
+ selectedRowKeys.value.length === 1 &&
+ selectedRowKeys.value[0] === recordId
+ ) {
+ return;
+ }
+
+ selectedRowKeys.value = [recordId];
+ selectedRows.value = spends.value.filter(
+ (item) => item[SpendInfoFields.ID] === recordId,
+ );
+};
+
+const customRow = (record) => {
+ return {
+ onClick: () => {
+ if (record[BaseFields.IS_DELETED] === 1) {
+ return;
+ }
+ selectRow(record);
+ },
+ onContextmenu: (event) => {
+ if (record[BaseFields.IS_DELETED] === 1) {
+ return;
+ }
+
+ event.preventDefault();
+ selectRow(record);
+ contextMenuRecord.value = record;
+ contextMenuX.value = event.clientX;
+ contextMenuY.value = event.clientY;
+ contextMenuVisible.value = true;
+ },
+ };
+};
+
// 处理单行选择变化
const handleSelectChange = (record, e) => {
const checked = e.target.checked;
@@ -418,13 +532,19 @@ const handleFilterReset = (vals) => {
const getStateColor = (state) => StateColors[state] || "#888";
-const fetchSpendInfoData = async () => {
+const fetchSpendInfoData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchSpendInfos({
- [SpendInfoFields.PAGE]: pagination.current,
- [SpendInfoFields.PAGE_SIZE]: pagination.pageSize,
- [SpendInfoFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -432,7 +552,10 @@ const fetchSpendInfoData = async () => {
[SpendInfoFields.ID]: item[SpendInfoFields.ID],
[SpendInfoFields.ROWVERSION]: item[SpendInfoFields.ROWVERSION],
[SpendInfoFields.NUMBER]: item[SpendInfoFields.NUMBER],
+ [SpendInfoFields.ROOM_ID]: item[SpendInfoFields.ROOM_ID],
[SpendInfoFields.ROOM_NO]: item[SpendInfoFields.ROOM_NO],
+ [SpendInfoFields.ROOM_LOCATOR]:
+ item[SpendInfoFields.ROOM_LOCATOR] || item[SpendInfoFields.ROOM_NO],
[SpendInfoFields.CUSTO_NO]: item[SpendInfoFields.CUSTO_NO],
[SpendInfoFields.NAME]: item[SpendInfoFields.NAME],
[SpendInfoFields.AMOUNT]: item[SpendInfoFields.AMOUNT],
@@ -443,6 +566,10 @@ const fetchSpendInfoData = async () => {
[SpendInfoFields.TIME]: item[SpendInfoFields.TIME],
[SpendInfoFields.STATE]: item[SpendInfoFields.STATE],
[SpendInfoFields.STATE_NAME]: item[SpendInfoFields.STATE_NAME],
+ [SpendInfoFields.DATA_INS_USER]: item[SpendInfoFields.DATA_INS_USER],
+ [SpendInfoFields.DATA_INS_DATE]: item[SpendInfoFields.DATA_INS_DATE],
+ [SpendInfoFields.DATA_CHG_USER]: item[SpendInfoFields.DATA_CHG_USER],
+ [SpendInfoFields.DATA_CHG_DATE]: item[SpendInfoFields.DATA_CHG_DATE],
[SpendInfoFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -458,6 +585,57 @@ onMounted(() => {
fetchSpendInfoData();
});
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: spends.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = spends.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchSpendInfoData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: spends.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ spends.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchSpendInfoData();
@@ -468,7 +646,9 @@ const editSpend = (record) => {
modalTitle.value = t("message.updateSpend");
form[SpendInfoFields.ID] = record[SpendInfoFields.ID];
form[SpendInfoFields.NUMBER] = record[SpendInfoFields.NUMBER];
+ form[SpendInfoFields.ROOM_ID] = record[SpendInfoFields.ROOM_ID] ?? null;
form[SpendInfoFields.ROOM_NO] = record[SpendInfoFields.ROOM_NO];
+ form[SpendInfoFields.ROOM_LOCATOR] = record[SpendInfoFields.ROOM_LOCATOR];
form[SpendInfoFields.CUSTO_NO] = record[SpendInfoFields.CUSTO_NO];
form[SpendInfoFields.NAME] = record[SpendInfoFields.NAME];
form[SpendInfoFields.AMOUNT] = record[SpendInfoFields.AMOUNT];
diff --git a/src/views/customermanagement/CustomerTypeView.vue b/src/views/customermanagement/CustomerTypeView.vue
index 42ad87aa294a7a386c64bc7913731abbe5ff5859..cf67d721b806e6cdb5dd23d32e46ad03a5992c66 100644
--- a/src/views/customermanagement/CustomerTypeView.vue
+++ b/src/views/customermanagement/CustomerTypeView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -166,6 +201,26 @@
{{ selectedRecord?.[CustomerTypeFields.DISCOUNT] }}%
+
+
+ {{ selectedRecord?.[CustomerTypeFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[CustomerTypeFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[CustomerTypeFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[CustomerTypeFields.DATA_CHG_DATE])
+ }}
+
+
@@ -176,27 +231,37 @@ import { ref, reactive, onMounted, computed } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
import { useI18n } from "vue-i18n";
+import dayjs from "dayjs";
+import useModalForm from "@/composables/useModalForm";
import { CustomerTypePermissions } from "@/common/permissioncode";
import {
CustomerTypeFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/customertype.entity";
+} from "@/entities/customermanagement/customertype.entity";
import {
fetchCustomerTypes,
addCustomerType,
updateCustomerType,
deleteCustomerType,
-} from "@/api/customertypeapi";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+} from "@/api/customermanagement/customertypeapi";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ exportTableToCsv,
+ formatDateTime,
+} from "@/utils/index";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getCustomerTypeFilterConfig } from "@/filters/customertype.filter.config";
+import { getCustomerTypeFilterConfig } from "@/filters/customermanagement/customertype.filter.config";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const customerTypes = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -211,6 +276,9 @@ const currentFilterParams = reactive({});
const form = reactive({ ...initialFormValues });
+const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
+
const pagination = reactive({
current: 1,
pageSize: 15,
@@ -220,8 +288,12 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
+
const translatedPageTitle = computed(() => t(getPageTitle(route.path)));
-const rules = getFormRules(t);
const customerTypeFilterConfig = computed(() => getCustomerTypeFilterConfig(t));
@@ -233,7 +305,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[CustomerTypeFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -346,11 +418,20 @@ const columns = computed(() => {
];
});
-const buildServerParamsFromFilters = () => {
- let params = {};
- const fields = internalfinanceFilterConfig.value?.fields || [];
+const buildServerParamsFromFilters = (sourceValues = filterValues.value) => {
+ const params = {};
+ const fields = [...(customerTypeFilterConfig.value?.fields || [])];
+ if (
+ Object.prototype.hasOwnProperty.call(sourceValues, BaseFields.IS_DELETED) &&
+ !fields.some((field) => field.name === BaseFields.IS_DELETED)
+ ) {
+ fields.push({
+ name: BaseFields.IS_DELETED,
+ type: "select",
+ });
+ }
fields.forEach((f) => {
- const val = filterValues[f.name];
+ const val = sourceValues[f.name];
if (
val === null ||
val === undefined ||
@@ -386,40 +467,41 @@ const buildServerParamsFromFilters = () => {
};
const handleFilterSearch = (values) => {
- Object.assign(filterValues, values);
- const built = buildServerParamsFromFilters();
+ filterValues.value = { ...values };
+ const built = buildServerParamsFromFilters(values);
Object.keys(currentFilterParams).forEach(
(k) => delete currentFilterParams[k],
);
Object.assign(currentFilterParams, built);
pagination.current = 1;
- clearSelection(); // 重置选择
fetchCustomerTypeData();
};
const handleFilterReset = (vals) => {
- if (vals && typeof vals === "object") {
- // filterValues is reactive
- Object.keys(filterValues).forEach((k) => delete filterValues[k]);
- Object.assign(filterValues, vals);
- } else {
- Object.keys(filterValues).forEach((k) => (filterValues[k] = null));
- }
Object.keys(currentFilterParams).forEach(
(k) => delete currentFilterParams[k],
);
- pagination.current = 1;
- clearSelection(); // 重置选择
+ if (vals && typeof vals === "object") {
+ filterValues.value = { ...vals };
+ } else {
+ filterValues.value = {};
+ }
fetchCustomerTypeData();
};
-const fetchCustomerTypeData = async () => {
+const fetchCustomerTypeData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchCustomerTypes({
- [CustomerTypeFields.PAGE]: pagination.current,
- [CustomerTypeFields.PAGE_SIZE]: pagination.pageSize,
- [CustomerTypeFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -429,8 +511,15 @@ const fetchCustomerTypeData = async () => {
[CustomerTypeFields.NUMBER]: item[CustomerTypeFields.NUMBER],
[CustomerTypeFields.NAME]: item[CustomerTypeFields.NAME],
[CustomerTypeFields.DISCOUNT]: item[CustomerTypeFields.DISCOUNT],
- [CustomerTypeFields.IS_DELETED]:
- item[CustomerTypeFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [CustomerTypeFields.DATA_INS_USER]:
+ item[CustomerTypeFields.DATA_INS_USER],
+ [CustomerTypeFields.DATA_INS_DATE]:
+ item[CustomerTypeFields.DATA_INS_DATE],
+ [CustomerTypeFields.DATA_CHG_USER]:
+ item[CustomerTypeFields.DATA_CHG_USER],
+ [CustomerTypeFields.DATA_CHG_DATE]:
+ item[CustomerTypeFields.DATA_CHG_DATE],
[CustomerTypeFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -446,7 +535,13 @@ onMounted(() => {
fetchCustomerTypeData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertCustomerType");
form[CustomerTypeFields.NUMBER] = null;
@@ -455,6 +550,57 @@ const showModal = () => {
form[CustomerTypeFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: customerTypes.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = customerTypes.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchCustomerTypeData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: customerTypes.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ customerTypes.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchCustomerTypeData();
@@ -505,7 +651,7 @@ const handleModalCancel = () => {
const restoreCustomerType = async (record) => {
try {
- record[CustomerTypeFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
var response = await updateCustomerType(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/customermanagement/CustomerView.vue b/src/views/customermanagement/CustomerView.vue
index 12fd099abfd335f38f6958af1166d80d23ba9d2f..0d617c601a854c87c309eadb21e725126762a676 100644
--- a/src/views/customermanagement/CustomerView.vue
+++ b/src/views/customermanagement/CustomerView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
+
+
+
+
+
+ {{ copyCustomerNumberText }}
+
+
+
+
+
@@ -133,7 +195,10 @@
-
+
@@ -151,7 +216,10 @@
:label="customerBirthdayLabel"
:name="CustomerFields.BIRTH_DATE"
>
-
+
@@ -179,18 +247,64 @@
:label="customerPassportIDLabel"
:name="CustomerFields.ID_NUMBER"
>
-
+
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
@@ -219,10 +333,10 @@
{{ formatDate(selectedRecord?.[CustomerFields.BIRTH_DATE]) }}
- {{ selectedRecord?.[CustomerFields.TYPE] }}
+ {{ selectedRecord?.[CustomerFields.TYPE_NAME] }}
- {{ selectedRecord?.[CustomerFields.PASSPORTTYPE] }}
+ {{ selectedRecord?.[CustomerFields.PASSPORTNAME] }}
{{ selectedRecord?.[CustomerFields.ID_NUMBER] }}
@@ -233,6 +347,22 @@
{{ selectedRecord?.[CustomerFields.ADDRESS] }}
+
+
+ {{ selectedRecord?.[CustomerFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[CustomerFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[CustomerFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[CustomerFields.DATA_CHG_DATE]) }}
+
+
@@ -254,7 +384,12 @@ import {
addCustomer,
updateCustomer,
deleteCustomer,
-} from "@/api/customerapi";
+} from "@/api/customermanagement/customerapi";
+import {
+ checkinRoomByReservation,
+ fetchRoomPricingOptions,
+} from "@/api/roominformation/roomapi";
+import { fetchReserByRoom } from "@/api/roominformation/reserapi";
import { fetchCardCode } from "@/api/utilityapi";
import { debounce } from "lodash-es";
import {
@@ -263,29 +398,38 @@ import {
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/customer.entity";
-import { CustomerTypeFields } from "@/entities/customertype.entity";
-import { PassportFields } from "@/entities/passport.entity";
-import { fetchCanUsePassports } from "@/api/passportapi";
-import { fetchCanUseCustomerTypes } from "@/api/custotypeapi";
+} from "@/entities/customermanagement/customer.entity";
+import { RoomFields } from "@/entities/roominformation/room.entity";
+import { ReserFields } from "@/entities/roominformation/reser.entity";
+import { CustomerTypeFields } from "@/entities/customermanagement/customertype.entity";
+import { PassportFields } from "@/entities/base/passport.entity";
+import { BaseFields } from "@/entities/common.entity";
+import { fetchCanUsePassports } from "@/api/base/passportapi";
+import { fetchCanUseCustomerTypes } from "@/api/customermanagement/custotypeapi";
import {
+ formatCurrency,
formatDate,
showErrorNotification,
showSuccessNotification,
} from "@/utils/index";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import { CustomerPermissions } from "@/common/permissioncode";
import dayjs from "dayjs";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getCustomerFilterConfig } from "@/filters/customer.filter.config";
+import { getCustomerFilterConfig } from "@/filters/customermanagement/customer.filter.config";
+import { exportTableToCsv, formatDateTime } from "@/utils/index";
+import { pcaa } from "area-data";
-const { t } = useI18n();
+const { t, locale } = useI18n();
const route = useRoute();
const router = useRouter();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const customers = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -300,10 +444,116 @@ const selectedRecord = ref(null);
const selectedRowKeys = ref([]);
const selectedRows = ref([]);
const currentFilterParams = reactive({});
+const contextMenuVisible = ref(false);
+const contextMenuX = ref(0);
+const contextMenuY = ref(0);
+const contextMenuRecord = ref(null);
+const reservationPricingOptions = ref([]);
+const reservationPricingLoading = ref(false);
+
+// 省市区联动相关变量
+// 将 pcaa 数据转换为级联选择器格式
+const convertPcaaToCascaderOptions = (pcaaData) => {
+ const options = [];
+ const provinceCode = "86"; // 中国的省份代码
+
+ // 获取省份数据
+ const provinces = pcaaData[provinceCode];
+ if (!provinces) return options;
+
+ for (const [provinceCode, provinceName] of Object.entries(provinces)) {
+ const provinceOption = {
+ value: provinceCode,
+ label: provinceName,
+ children: [],
+ };
+
+ // 获取城市数据
+ const cities = pcaaData[provinceCode];
+ if (cities) {
+ for (const [cityCode, cityName] of Object.entries(cities)) {
+ const cityOption = {
+ value: cityCode,
+ label: cityName,
+ children: [],
+ };
+
+ // 获取区县数据
+ const districts = pcaaData[cityCode];
+ if (districts) {
+ for (const [districtCode, districtName] of Object.entries(
+ districts,
+ )) {
+ cityOption.children.push({
+ value: districtCode,
+ label: districtName,
+ });
+ }
+ }
+
+ // 只有当有子节点时才添加
+ if (cityOption.children.length > 0) {
+ provinceOption.children.push(cityOption);
+ } else {
+ provinceOption.children.push({
+ value: cityCode,
+ label: cityName,
+ });
+ }
+ }
+ }
+
+ options.push(provinceOption);
+ }
+
+ return options;
+};
+
+const regionOptions = ref(convertPcaaToCascaderOptions(pcaa));
+const selectedRegion = ref([]);
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
+
+// 根据省市区名称查找对应的地区代码
+const findRegionCode = (province, city, district) => {
+ if (!province) return [];
+
+ // 在省数据中查找匹配的省份
+ const provinceData = regionOptions.value.find(
+ (p) =>
+ p.label === province ||
+ p.label.replace(/省|市|自治区|特别行政区/, "") === province,
+ );
+
+ if (!provinceData || !provinceData.children) return [];
+
+ // 在市数据中查找匹配的城市
+ const cityData = provinceData.children.find(
+ (c) => c.label === city || c.label.replace(/市|地区|盟/, "") === city,
+ );
+
+ if (!cityData || !cityData.children) {
+ // 如果找不到市,返回省份代码
+ return [provinceData.value];
+ }
+
+ // 在区县数据中查找匹配的区县
+ const districtData = cityData.children.find(
+ (d) =>
+ d.label === district || d.label.replace(/市|区|县|旗/, "") === district,
+ );
+
+ if (!districtData) {
+ // 如果找不到区县,返回省市代码
+ return [provinceData.value, cityData.value];
+ }
+
+ // 返回完整的省市县代码
+ return [provinceData.value, cityData.value, districtData.value];
+};
const customerNoLabel = computed(() => t("message.customerNo"));
const customerNameLabel = computed(() => t("message.customerName"));
@@ -316,6 +566,117 @@ const customerTypeLabel = computed(() => t("message.customerType"));
const customerTelLabel = computed(() => t("message.customerTel"));
const customerBirthdayLabel = computed(() => t("message.customerBirth"));
const customerAddressLabel = computed(() => t("message.customerAddress"));
+const roomLocatorLabel = computed(() => t("message.roomLocator"));
+const pricingOptionLabel = computed(() => t("message.pricingOption"));
+const copyCustomerNumberText = computed(() => {
+ if (t("message.copyCustomerNumber")) {
+ return t("message.copyCustomerNumber");
+ }
+
+ return locale.value === "zh-CN" ? "复制客户编号" : "Copy Customer Number";
+});
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
+
+const getContextMenuPopupContainer = () => {
+ if (typeof window !== "undefined" && window.document?.body) {
+ return window.document.body;
+ }
+
+ if (typeof document !== "undefined" && document.body) {
+ return document.body;
+ }
+
+ return null;
+};
+
+const getCustomerMessage = (key, zhFallback, enFallback) => {
+ if (t(key)) {
+ return t(key);
+ }
+
+ return locale.value === "zh-CN" ? zhFallback : enFallback;
+};
+
+const getQueryString = (value) => {
+ if (Array.isArray(value)) return String(value[0] || "").trim();
+ return String(value || "").trim();
+};
+
+const getQueryNumber = (value) => {
+ const parsed = Number(getQueryString(value));
+ return Number.isFinite(parsed) ? parsed : null;
+};
+
+const reservationCheckinContext = computed(() => {
+ if (getQueryString(route.query.flow) !== "reservation-checkin") {
+ return null;
+ }
+ return {
+ roomId: getQueryNumber(route.query.roomId),
+ roomNumber: getQueryString(route.query.roomNumber),
+ roomLocator: getQueryString(route.query.roomLocator),
+ reservationId: getQueryString(route.query.reservationId),
+ reservationCustomerName: getQueryString(
+ route.query.reservationCustomerName,
+ ),
+ reservationPhoneNumber: getQueryString(route.query.reservationPhoneNumber),
+ returnPath: getQueryString(route.query.returnPath) || "/roommap",
+ };
+});
+
+const isReservationCheckinFlow = computed(() =>
+ Boolean(reservationCheckinContext.value),
+);
+
+const buildReservationPricingOptions = (pricingData) => {
+ const pricingItems = Array.isArray(pricingData?.PricingItems)
+ ? pricingData.PricingItems
+ : [];
+
+ return pricingItems.map((item) => ({
+ label: `${item?.PricingName || item?.PricingCode || "-"} (${formatCurrency(
+ item?.RoomRent ?? 0,
+ )} / ${formatCurrency(item?.RoomDeposit ?? 0)})`,
+ value: String(item?.PricingCode || "").trim(),
+ isDefault: Boolean(item?.IsDefault),
+ }));
+};
+
+const loadReservationPricingOptions = async () => {
+ if (!reservationCheckinContext.value) {
+ reservationPricingOptions.value = [];
+ form[RoomFields.PRICING_CODE] = "";
+ return;
+ }
+
+ reservationPricingLoading.value = true;
+ try {
+ const pricingData = await fetchRoomPricingOptions({
+ [RoomFields.ID]: reservationCheckinContext.value.roomId ?? null,
+ [RoomFields.NO]: reservationCheckinContext.value.roomNumber || "",
+ });
+
+ const options = buildReservationPricingOptions(pricingData);
+ reservationPricingOptions.value = options;
+
+ if (!form[RoomFields.PRICING_CODE]) {
+ form[RoomFields.PRICING_CODE] =
+ String(pricingData?.CurrentPricingCode || "").trim() ||
+ options.find((item) => item.isDefault)?.value ||
+ options[0]?.value ||
+ "";
+ }
+ } catch (error) {
+ reservationPricingOptions.value = [];
+ form[RoomFields.PRICING_CODE] = "";
+ showErrorNotification(error.message || t("message.pleaseTryAgainLater"));
+ } finally {
+ reservationPricingLoading.value = false;
+ }
+};
const customerFilterConfig = computed(() =>
getCustomerFilterConfig(t, customerTypeOptions.value),
@@ -327,19 +688,33 @@ const filteredColumns = computed(() =>
const queryAddress = debounce(async (idCard) => {
try {
- if (!idCard || idCard.length < 15) return;
+ if (!idCard || idCard.length < 15) {
+ return;
+ }
+ // 继续查询地址(如果后端支持)
const result = await fetchCardCode({
- IdentityCardNumber: idCard,
+ IdentityCardNumber: idCard.trim(),
});
if (result?.Source) {
const { Province = "", City = "", District = "" } = result?.Source || {};
+
+ // 尝试根据省市区名称匹配并设置级联选择器
+ const matchedRegion = findRegionCode(Province, City, District);
+ if (matchedRegion && matchedRegion.length > 0) {
+ selectedRegion.value = matchedRegion;
+ }
+
const fullAddress = [Province, City, District]
.filter((part) => part?.trim())
.join("");
if (fullAddress) {
- form[CustomerFields.ADDRESS] = fullAddress;
+ // 检查当前地址是否已经包含这个地址,避免重复
+ const currentAddress = form[CustomerFields.ADDRESS] || "";
+ if (!currentAddress.includes(fullAddress)) {
+ form[CustomerFields.ADDRESS] = fullAddress;
+ }
}
}
} catch (e) {
@@ -347,6 +722,23 @@ const queryAddress = debounce(async (idCard) => {
}
}, 500);
+// 处理省市区选择变化 - 将选择的地区合并到地址字段
+const handleRegionChange = (values, options) => {
+ if (!values || values.length === 0) return;
+
+ // 构建完整地址(从选择器选项中获取)
+ const regionLabels = options
+ .filter((opt) => opt && opt.label)
+ .map((opt) => opt.label);
+
+ const regionStr = regionLabels.join("");
+
+ if (!regionStr) return;
+
+ // 合并地区和详细地址
+ form[CustomerFields.ADDRESS] = regionStr;
+};
+
watch(
() => form[CustomerFields.ID_NUMBER],
(newVal) => {
@@ -354,6 +746,23 @@ watch(
},
);
+watch(
+ reservationPricingOptions,
+ (options) => {
+ if (
+ !isReservationCheckinFlow.value ||
+ form[CustomerFields.MODIFYSTATUS] === "update" ||
+ form[RoomFields.PRICING_CODE]
+ ) {
+ return;
+ }
+
+ form[RoomFields.PRICING_CODE] =
+ options.find((item) => item.isDefault)?.value || options[0]?.value || "";
+ },
+ { deep: true },
+);
+
onBeforeUnmount(() => {
queryAddress.cancel();
});
@@ -366,7 +775,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[CustomerFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -389,14 +798,88 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[CustomerFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
},
+ onContextmenu: (event) => {
+ if (record[BaseFields.IS_DELETED] === 1) {
+ return;
+ }
+
+ event.preventDefault();
+ selectRow(record);
+ contextMenuRecord.value = record;
+ contextMenuX.value = event.clientX;
+ contextMenuY.value = event.clientY;
+ contextMenuVisible.value = true;
+ },
};
};
+const copyTextToClipboard = async (text) => {
+ if (navigator?.clipboard?.writeText) {
+ await navigator.clipboard.writeText(text);
+ return true;
+ }
+
+ const textArea = document.createElement("textarea");
+ textArea.value = text;
+ textArea.style.position = "fixed";
+ textArea.style.opacity = "0";
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+
+ try {
+ return document.execCommand("copy");
+ } catch (error) {
+ return false;
+ } finally {
+ document.body.removeChild(textArea);
+ }
+};
+
+const handleContextMenuClick = async ({ key }) => {
+ if (key !== "copyCustomerNumber") {
+ contextMenuVisible.value = false;
+ return;
+ }
+
+ const customerNumber = String(
+ contextMenuRecord.value?.[CustomerFields.NUMBER] || "",
+ ).trim();
+ if (!customerNumber) {
+ contextMenuVisible.value = false;
+ return;
+ }
+
+ try {
+ const copied = await copyTextToClipboard(customerNumber);
+ if (!copied) {
+ throw new Error("copy failed");
+ }
+ showSuccessNotification(
+ getCustomerMessage(
+ "message.customerNumberCopied",
+ "客户编号已复制到剪贴板",
+ "Customer number copied to clipboard",
+ ),
+ );
+ } catch (error) {
+ showErrorNotification(
+ getCustomerMessage(
+ "message.customerNumberCopyFailed",
+ "复制客户编号失败",
+ "Failed to copy customer number",
+ ),
+ );
+ } finally {
+ contextMenuVisible.value = false;
+ }
+};
+
// 处理单行选择变化
const handleSelectChange = (record, e) => {
const checked = e.target.checked;
@@ -476,7 +959,7 @@ const columns = computed(() => {
{
title: t("message.action"),
key: "action",
- width: 100,
+ width: 150,
fixed: "left",
align: "center",
},
@@ -489,7 +972,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...customerFilterConfig.value?.fields.map((f) => f.name),
- CustomerFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -504,7 +987,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === CustomerFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -576,13 +1059,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchCustomerData = async () => {
+const fetchCustomerData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchCustomers({
- [CustomerFields.PAGE]: pagination.current,
- [CustomerFields.PAGE_SIZE]: pagination.pageSize,
- [CustomerFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -604,7 +1093,11 @@ const fetchCustomerData = async () => {
[CustomerFields.ID_NUMBER]: item[CustomerFields.ID_NUMBER],
[CustomerFields.PHONE]: item[CustomerFields.PHONE],
[CustomerFields.ADDRESS]: item[CustomerFields.ADDRESS],
- [CustomerFields.IS_DELETED]: item[CustomerFields.IS_DELETED],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED],
+ [CustomerFields.DATA_INS_USER]: item[CustomerFields.DATA_INS_USER],
+ [CustomerFields.DATA_INS_DATE]: item[CustomerFields.DATA_INS_DATE],
+ [CustomerFields.DATA_CHG_USER]: item[CustomerFields.DATA_CHG_USER],
+ [CustomerFields.DATA_CHG_DATE]: item[CustomerFields.DATA_CHG_DATE],
[CustomerFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -640,31 +1133,131 @@ const fetchSelectCustoTypes = async () => {
}
};
+const extractReservationFromResponse = (response) => {
+ if (!response) return null;
+ if (Array.isArray(response.Items)) return response.Items[0] || null;
+ if (Array.isArray(response)) return response[0] || null;
+ return response;
+};
+
+const resolveReservationIdByRoom = async (roomId, roomNumber) => {
+ if (!roomId && !roomNumber) return "";
+ try {
+ const reservationResponse = await fetchReserByRoom({
+ [ReserFields.ROOM_ID]: roomId,
+ [ReserFields.ROOMNUMBER]: roomNumber,
+ [BaseFields.IS_DELETED]: 0,
+ });
+ const reservation = extractReservationFromResponse(reservationResponse);
+ return reservation?.[ReserFields.NUMBER] || "";
+ } catch (error) {
+ showErrorNotification(error.message || t("message.pleaseTryAgainLater"));
+ return "";
+ }
+};
+
onMounted(() => {
fetchCustomerData();
fetchSelectPassports();
fetchSelectCustoTypes();
+ loadReservationPricingOptions();
+ if (
+ reservationCheckinContext.value?.roomId ||
+ reservationCheckinContext.value?.roomNumber
+ ) {
+ showErrorNotification(t("message.checkinNeedCustomerRegistration"));
+ showModal({
+ customerName: reservationCheckinContext.value.reservationCustomerName,
+ phoneNumber: reservationCheckinContext.value.reservationPhoneNumber,
+ });
+ }
});
-const showModal = () => {
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+ // 重置级联选择器
+ selectedRegion.value = [];
+};
+
+const showModal = (prefill = null) => {
+ const prefillCustomerName = String(prefill?.customerName || "").trim();
+ const prefillPhoneNumber = String(prefill?.phoneNumber || "").trim();
+
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertCustomer");
- form[CustomerFields.NUMBER] = generateSnowflakeId({
+ form[CustomerFields.NUMBER] = generateUniqueId({
prefix: "TS-",
separator: null,
});
- form[CustomerFields.NAME] = "";
+ form[CustomerFields.NAME] = prefillCustomerName;
form[CustomerFields.GENDER] = null;
form[CustomerFields.BIRTH_DATE] = null;
form[CustomerFields.TYPE] = null;
form[CustomerFields.PASSPORTTYPE] = null;
form[CustomerFields.ID_NUMBER] = "";
- form[CustomerFields.PHONE] = "";
+ form[CustomerFields.PHONE] = prefillPhoneNumber;
form[CustomerFields.ADDRESS] = "";
+ form[RoomFields.PRICING_CODE] =
+ reservationPricingOptions.value.find((item) => item.isDefault)?.value ||
+ reservationPricingOptions.value[0]?.value ||
+ "";
form[CustomerFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: customers.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = customers.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchCustomerData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: customers.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ customers.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchCustomerData();
@@ -679,7 +1272,7 @@ const goPermissionRelation = (record) => {
const editCustomer = (record) => {
modalVisible.value = true;
- modalTitle.value = t("message.updateDepartment");
+ modalTitle.value = t("message.updateCustomer");
form[CustomerFields.ID] = record[CustomerFields.ID];
form[CustomerFields.NUMBER] = record[CustomerFields.NUMBER];
form[CustomerFields.NAME] = record[CustomerFields.NAME];
@@ -693,19 +1286,122 @@ const editCustomer = (record) => {
form[CustomerFields.PHONE] = record[CustomerFields.PHONE];
form[CustomerFields.ADDRESS] = record[CustomerFields.ADDRESS];
+ // 重置级联选择器
+ selectedRegion.value = [];
+
+ // 尝试根据地址匹配并设置级联选择器
+ const address = record[CustomerFields.ADDRESS] || "";
+ if (address) {
+ // 提取省市区信息
+ const matchedRegion = parseAddressToRegion(address);
+ if (matchedRegion && matchedRegion.length > 0) {
+ selectedRegion.value = matchedRegion;
+ }
+ }
+
form[CustomerFields.MODIFYSTATUS] = "update";
};
+// 根据地址字符串解析出省市区代码
+const parseAddressToRegion = (address) => {
+ if (!address) return [];
+
+ // 尝试匹配省份
+ for (const province of regionOptions.value) {
+ if (address.includes(province.label)) {
+ // 找到了省份,尝试找市
+ if (province.children) {
+ for (const city of province.children) {
+ if (address.includes(city.label)) {
+ // 找到了市,尝试找区县
+ if (city.children) {
+ for (const district of city.children) {
+ if (address.includes(district.label)) {
+ return [province.value, city.value, district.value];
+ }
+ }
+ }
+ return [province.value, city.value];
+ }
+ }
+ }
+ return [province.value];
+ }
+ }
+ return [];
+};
+
const handleModalOk = async () => {
try {
await formRef.value.validate();
confirmLoading.value = true;
+ const birthDate = form[CustomerFields.BIRTH_DATE]
+ ? form[CustomerFields.BIRTH_DATE].format("YYYY-MM-DD")
+ : null;
+
+ const isReservationCheckinInsertFlow =
+ form[CustomerFields.MODIFYSTATUS] !== "update" &&
+ !!(
+ reservationCheckinContext.value?.roomId ||
+ reservationCheckinContext.value?.roomNumber
+ );
+
+ if (isReservationCheckinInsertFlow) {
+ if (
+ reservationPricingOptions.value.length > 0 &&
+ !form[RoomFields.PRICING_CODE]
+ ) {
+ showErrorNotification(
+ t("message.required", { field: t("message.pricingOption") }),
+ );
+ return;
+ }
+
+ const { roomId, roomNumber, returnPath } =
+ reservationCheckinContext.value;
+ const reservationId =
+ reservationCheckinContext.value.reservationId ||
+ (await resolveReservationIdByRoom(roomId, roomNumber));
+
+ if (!reservationId) {
+ showErrorNotification(t("message.reservationNotFoundForRoom"));
+ return;
+ }
+
+ const response = await checkinRoomByReservation({
+ [CustomerFields.NUMBER]: (form[CustomerFields.NUMBER] || "").trim(),
+ [CustomerFields.NAME]: form[CustomerFields.NAME],
+ [CustomerFields.GENDER]: form[CustomerFields.GENDER],
+ [CustomerFields.PASSPORTTYPE]: form[CustomerFields.PASSPORTTYPE],
+ [CustomerFields.PHONE]: form[CustomerFields.PHONE],
+ [CustomerFields.BIRTH_DATE]: birthDate,
+ [CustomerFields.ID_NUMBER]: form[CustomerFields.ID_NUMBER],
+ [CustomerFields.ADDRESS]: form[CustomerFields.ADDRESS],
+ [CustomerFields.TYPE]: form[CustomerFields.TYPE],
+ [RoomFields.ID]: roomId,
+ [RoomFields.NO]: roomNumber,
+ [RoomFields.PRICING_CODE]: form[RoomFields.PRICING_CODE] || "",
+ [ReserFields.NUMBER]: reservationId,
+ });
+
+ if (response && response.Success) {
+ showSuccessNotification(t("message.operationSuccess"));
+ modalVisible.value = false;
+ clearSelection();
+ fetchCustomerData();
+ await router.replace(returnPath || "/roommap");
+ } else {
+ showErrorNotification(
+ response?.Message || t("message.pleaseTryAgainLater"),
+ );
+ }
+ return;
+ }
+
if (form[CustomerFields.MODIFYSTATUS] === "update") {
- var response = await updateCustomer({
+ const response = await updateCustomer({
...form,
- [CustomerFields.BIRTH_DATE]: form[CustomerFields.BIRTH_DATE]
- ? form[CustomerFields.BIRTH_DATE].format("YYYY-MM-DD")
- : null,
+ [CustomerFields.BIRTH_DATE]: birthDate,
});
if (response && response.Success) {
showSuccessNotification(t("message.updateSuccess"));
@@ -713,11 +1409,9 @@ const handleModalOk = async () => {
showErrorNotification(t("message.pleaseTryAgainLater"));
}
} else {
- var response = await addCustomer({
+ const response = await addCustomer({
...form,
- [CustomerFields.BIRTH_DATE]: form[CustomerFields.BIRTH_DATE]
- ? form[CustomerFields.BIRTH_DATE].format("YYYY-MM-DD")
- : null,
+ [CustomerFields.BIRTH_DATE]: birthDate,
});
if (response && response.Success) {
showSuccessNotification(t("message.addSuccess"));
@@ -741,7 +1435,7 @@ const handleModalCancel = () => {
const restoreCustomer = async (record) => {
try {
- record[CustomerFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateCustomer(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
@@ -764,3 +1458,13 @@ const handleSorterChange = (pagination, filters, sorter) => {
sortedInfo.value = sorter;
};
+
+
diff --git a/src/views/customermanagement/VipLevelView.vue b/src/views/customermanagement/VipLevelView.vue
index 8289ae53dda87d23ed1b5af883bd871212f26bb6..f919238b0d79918811000dab850fa2682afdcd92 100644
--- a/src/views/customermanagement/VipLevelView.vue
+++ b/src/views/customermanagement/VipLevelView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -123,7 +158,10 @@
-
+
@@ -179,6 +217,22 @@
{{ selectedRecord?.[VipRuleFields.CUSTOMER_TYPE_NAME] }}
+
+
+ {{ selectedRecord?.[VipRuleFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[VipRuleFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[VipRuleFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[VipRuleFields.DATA_CHG_DATE]) }}
+
+
@@ -193,27 +247,32 @@ import {
addVipRule,
updateVipRule,
deleteVipRule,
-} from "@/api/vipruleapi";
+} from "@/api/customermanagement/vipruleapi";
import {
VipRuleFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/viprule.entity";
-import { fetchCustomerTypes } from "@/api/custotypeapi";
+} from "@/entities/customermanagement/viprule.entity";
+import { fetchCustomerTypes } from "@/api/customermanagement/custotypeapi";
import { showErrorNotification, showSuccessNotification } from "@/utils/index";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import { VipLevelPermissions } from "@/common/permissioncode";
import { formatCurrency } from "@/utils/index";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getVipRuleFilterConfig } from "@/filters/viprule.filter.config";
+import { getVipRuleFilterConfig } from "@/filters/customermanagement/viprule.filter.config";
+import { exportTableToCsv, formatDateTime } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const viprules = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -231,6 +290,7 @@ const currentFilterParams = reactive({});
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const vipruleIdLabel = computed(() => t("message.vipRuleId"));
const vipruleNameLabel = computed(() => t("message.vipRuleName"));
@@ -238,6 +298,10 @@ const vipruleValueLabel = computed(() => t("message.vipRuleValue"));
const vipruleCustomerTypeLabel = computed(() =>
t("message.vipRuleCustomerType"),
);
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const filteredColumns = computed(() =>
columns.value.filter((column) => !column.hidden),
@@ -264,7 +328,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[VipRuleFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -287,7 +351,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[VipRuleFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -412,13 +476,19 @@ const handleFilterReset = (vals) => {
fetchVipRuleData();
};
-const fetchVipRuleData = async () => {
+const fetchVipRuleData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchVipRules({
- [VipRuleFields.PAGE]: pagination.current,
- [VipRuleFields.PAGE_SIZE]: pagination.pageSize,
- [VipRuleFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -431,7 +501,11 @@ const fetchVipRuleData = async () => {
[VipRuleFields.CUSTOMER_TYPE_ID]: item[VipRuleFields.CUSTOMER_TYPE_ID],
[VipRuleFields.CUSTOMER_TYPE_NAME]:
item[VipRuleFields.CUSTOMER_TYPE_NAME],
- [VipRuleFields.IS_DELETED]: item[VipRuleFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [VipRuleFields.DATA_INS_USER]: item[VipRuleFields.DATA_INS_USER],
+ [VipRuleFields.DATA_INS_DATE]: item[VipRuleFields.DATA_INS_DATE],
+ [VipRuleFields.DATA_CHG_USER]: item[VipRuleFields.DATA_CHG_USER],
+ [VipRuleFields.DATA_CHG_DATE]: item[VipRuleFields.DATA_CHG_DATE],
[VipRuleFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -446,8 +520,8 @@ const fetchVipRuleData = async () => {
const fetchSelectCustomerTypes = async () => {
try {
const result = await fetchCustomerTypes({
- [VipRuleFields.IS_DELETED]: 0,
- [VipRuleFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
custoTypeOptions.value = result.Items.map((item) => ({
label: item.CustomerTypeName,
@@ -463,10 +537,16 @@ onMounted(() => {
fetchSelectCustomerTypes();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertVipRule");
- form[VipRuleFields.NUMBER] = generateSnowflakeId({
+ form[VipRuleFields.NUMBER] = generateUniqueId({
prefix: "VR-",
separator: null,
});
@@ -476,6 +556,57 @@ const showModal = () => {
form[VipRuleFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: viprules.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = viprules.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchVipRuleData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: viprules.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ viprules.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchVipRuleData();
@@ -489,7 +620,7 @@ const editVipRule = (record) => {
form[VipRuleFields.NAME] = record[VipRuleFields.NAME];
form[VipRuleFields.VALUE] = record[VipRuleFields.VALUE];
form[VipRuleFields.CUSTOMER_TYPE_ID] = record[VipRuleFields.CUSTOMER_TYPE_ID];
- form[VipRuleFields.IS_DELETED] = record[[VipRuleFields.IS_DELETED]];
+ form[BaseFields.IS_DELETED] = record[[BaseFields.IS_DELETED]];
form[VipRuleFields.MODIFYSTATUS] = "update";
};
@@ -528,7 +659,7 @@ const handleModalCancel = () => {
const restoreVipRule = async (record) => {
try {
- record[VipRuleFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateVipRule(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/dashboard/DashboardView.vue b/src/views/dashboard/DashboardView.vue
index be58223baec7f83bfb8d5c8e63dff8650fe5e10e..1d08a967d6b17a804a1379ea50f8d2cf8c1d04cf 100644
--- a/src/views/dashboard/DashboardView.vue
+++ b/src/views/dashboard/DashboardView.vue
@@ -164,7 +164,9 @@
v-if="logisticsData.inventoryWarning.lowStockProducts?.length"
style="margin-top: 8px"
>
-
{{ $t("message.lowStock") }}
+
{{
+ $t("message.lowStock")
+ }}
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -123,7 +158,10 @@
{{ form[InternalFinanceFields.NUMBER] }}
-
+
-
+
{{ selectedRecord?.[InternalFinanceFields.ASSETSOURCE] }}
+
+
+ {{ selectedRecord?.[InternalFinanceFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDate(selectedRecord?.[InternalFinanceFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[InternalFinanceFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDate(selectedRecord?.[InternalFinanceFields.DATA_CHG_DATE])
+ }}
+
+
@@ -225,34 +286,39 @@ import {
addInternalFinance,
updateInternalFinance,
deleteInternalFinance,
-} from "@/api/internalfinanceapi";
+} from "@/api/finance/internalfinanceapi";
import {
InternalFinanceFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/internalfinance.entity";
-import { DepartmentFields } from "@/entities/department.entity";
-import { EmployeeFields } from "@/entities/employee.entity";
-import { fetchDepartments } from "@/api/departmentapi";
-import { fetchEmployees } from "@/api/employeeapi";
+} from "@/entities/finance/internalfinance.entity";
+import { DepartmentFields } from "@/entities/base/department.entity";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
+import { BaseFields } from "@/entities/common.entity";
+import { fetchDepartments } from "@/api/base/departmentapi";
+import { fetchEmployees } from "@/api/humanresourcemanagement/employeeapi";
import {
formatDate,
showErrorNotification,
showSuccessNotification,
} from "@/utils/index";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import { InternalFinancePermissions } from "@/common/permissioncode";
import dayjs from "dayjs";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getInternalFinanceFilterConfig } from "@/filters/internalfinance.filter.config";
+import { getInternalFinanceFilterConfig } from "@/filters/finance/internalfinance.filter.config";
+import { exportTableToCsv } from "@/utils/index";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const cashs = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -270,7 +336,7 @@ const internalfinanceFilterConfig = computed(() =>
personOptions.value,
),
);
-const filterValues = ref({ [InternalFinanceFields.IS_DELETED]: 0 });
+const filterValues = ref({ [BaseFields.IS_DELETED]: 0 });
const selectedRecord = ref(null);
const selectedRowKeys = ref([]);
const selectedRows = ref([]);
@@ -279,6 +345,7 @@ const currentFilterParams = reactive({});
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const cashNoLabel = computed(() => t("message.internalfinanceNo"));
const cashNameLabel = computed(() => t("message.internalfinanceName"));
@@ -287,6 +354,10 @@ const cashPersonLabel = computed(() => t("message.internalfinancePerson"));
const cashPriceLabel = computed(() => t("message.internalfinancePrice"));
const cashSourceLabel = computed(() => t("message.internalfinanceSource"));
const cashTimeLabel = computed(() => t("message.internalfinanceTime"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const filteredColumns = computed(() =>
columns.value.filter((column) => !column.hidden),
@@ -309,7 +380,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[InternalFinanceFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -332,7 +403,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[InternalFinanceFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -431,7 +502,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...internalfinanceFilterConfig.value?.fields.map((f) => f.name),
- InternalFinanceFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -446,7 +517,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === InternalFinanceFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -484,13 +555,19 @@ const buildServerParamsFromFilters = () => {
return params;
};
-const fetchInternalfinanceData = async () => {
+const fetchInternalfinanceData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchInternalFinances({
- [InternalFinanceFields.PAGE]: pagination.current,
- [InternalFinanceFields.PAGE_SIZE]: pagination.pageSize,
- [InternalFinanceFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -516,8 +593,15 @@ const fetchInternalfinanceData = async () => {
item[InternalFinanceFields.ACQUIREDBYEMPLOYEE],
[InternalFinanceFields.ACQUIREDBYEMPLOYEENAME]:
item[InternalFinanceFields.ACQUIREDBYEMPLOYEENAME],
- [InternalFinanceFields.IS_DELETED]:
- item[InternalFinanceFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [InternalFinanceFields.DATA_INS_USER]:
+ item[InternalFinanceFields.DATA_INS_USER],
+ [InternalFinanceFields.DATA_INS_DATE]:
+ item[InternalFinanceFields.DATA_INS_DATE],
+ [InternalFinanceFields.DATA_CHG_USER]:
+ item[InternalFinanceFields.DATA_CHG_USER],
+ [InternalFinanceFields.DATA_CHG_DATE]:
+ item[InternalFinanceFields.DATA_CHG_DATE],
[InternalFinanceFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -532,8 +616,8 @@ const fetchInternalfinanceData = async () => {
const fetchSelectDepartments = async () => {
try {
const result = await fetchDepartments({
- [DepartmentFields.IS_DELETED]: 0,
- [DepartmentFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
departmentOptions.value = result.Items.map((item) => ({
label: item[DepartmentFields.NAME],
@@ -547,8 +631,8 @@ const fetchSelectDepartments = async () => {
const fetchSelectPersons = async () => {
try {
const result = await fetchEmployees({
- [EmployeeFields.IS_DELETED]: 0,
- [EmployeeFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
personOptions.value = result.Items.map((item) => ({
label: item[EmployeeFields.NAME],
@@ -593,10 +677,16 @@ const handleFilterReset = (vals) => {
fetchInternalfinanceData();
};
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertInternalFinance");
- form[InternalFinanceFields.NUMBER] = generateSnowflakeId({
+ form[InternalFinanceFields.NUMBER] = generateUniqueId({
prefix: "IF-",
separator: null,
});
@@ -606,10 +696,61 @@ const showModal = () => {
form[InternalFinanceFields.ACQUISITIONDATE] = null;
form[InternalFinanceFields.ASSETSOURCE] = "";
form[InternalFinanceFields.ACQUIREDBYEMPLOYEE] = null;
- form[InternalFinanceFields.IS_DELETED] = 0;
+ form[BaseFields.IS_DELETED] = 0;
form[InternalFinanceFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: cashs.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = cashs.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchInternalfinanceData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: cashs.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ cashs.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchInternalfinanceData();
@@ -671,7 +812,7 @@ const handleModalCancel = () => {
const restoreInternalfinance = async (record) => {
try {
- record[InternalFinanceFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
var response = await updateInternalFinance(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/home/HomeView.vue b/src/views/home/HomeView.vue
index 2d50ee4d00c6b3d311b8fde00e157ababadc59a3..054a1693135c23407e4e64c7af6a5373d62dc5f7 100644
--- a/src/views/home/HomeView.vue
+++ b/src/views/home/HomeView.vue
@@ -162,19 +162,23 @@
@@ -561,14 +437,23 @@ onMounted(() => {
.welcome-card {
border-radius: 12px;
- background: linear-gradient(135deg, #f6fbff 0%, #eef5ff 100%);
+ background: linear-gradient(
+ 135deg,
+ color-mix(in srgb, var(--app-link-color) 8%, var(--app-surface-bg)) 0%,
+ color-mix(
+ in srgb,
+ var(--app-link-hover-color) 12%,
+ var(--app-surface-bg-soft)
+ )
+ 100%
+ );
}
.welcome-title {
margin-bottom: 10px;
font-size: 20px;
font-weight: 600;
- color: #1f2a44;
+ color: var(--app-heading-color);
}
.panel-card {
diff --git a/src/views/home/ProfileCenterView.vue b/src/views/home/ProfileCenterView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0ad7e8a10fb7bdf1713167e44c0ffd62d6bea031
--- /dev/null
+++ b/src/views/home/ProfileCenterView.vue
@@ -0,0 +1,1775 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ avatarFallbackText }}
+
+
+
+
+
+
+
+
+
+ {{ avatarFallbackText }}
+
+
+
+
{{ displayName }}
+
{{ displaySubtitle }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ currentUserNumber || "-" }}
+
+
+ {{ displayName }}
+
+
+ {{ accountTypeLabel }}
+
+
+ {{ currentAccount }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ identityLabel }}
+
+
+ {{ currentUserNumber || "-" }}
+
+
+
+
+
+
+
+
+
+ {{ t("message.homeAccountLabel") }}
+
+
+ {{ currentAccount }}
+
+
+
+
+
+ {{ t("message.staffDepartment") }}
+
+
+ {{
+ employeeProfile?.[EmployeeFields.DEPARTMENTNAME] ||
+ "-"
+ }}
+
+
+
+
+
+ {{ t("message.staffPosition") }}
+
+
+ {{
+ employeeProfile?.[EmployeeFields.POSITIONNAME] || "-"
+ }}
+
+
+
+
+
+ {{ t("message.adminType") }}
+
+
+ {{
+ adminProfile?.[AdministratorFields.TYPENAME] ||
+ adminProfile?.[AdministratorFields.TYPE] ||
+ "-"
+ }}
+
+
+
+
+
+ {{ t("message.isSuperAdminFlag") }}
+
+
+ {{ superAdminLabel }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.staffTime") }}
+
+
+ {{
+ formatDate(
+ employeeProfile?.[EmployeeFields.HIREDATE],
+ ) || "-"
+ }}
+
+
+
+
+
+ {{ t("message.staffBirthday") }}
+
+
+ {{
+ formatDate(
+ employeeProfile?.[EmployeeFields.DATEOFBIRTH],
+ ) || "-"
+ }}
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.cancel") }}
+
+
+ {{ t("message.save") }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.edit") }}
+
+
+
+
+
+ {{ currentUserNumber || "-" }}
+
+
+ {{ displayName }}
+
+
+ {{ currentAccount }}
+
+
+ {{
+ employeeProfile?.[EmployeeFields.DEPARTMENTNAME] || "-"
+ }}
+
+
+ {{ employeeProfile?.[EmployeeFields.POSITIONNAME] || "-" }}
+
+
+ {{
+ adminProfile?.[AdministratorFields.TYPENAME] ||
+ adminProfile?.[AdministratorFields.TYPE] ||
+ "-"
+ }}
+
+
+ {{ superAdminLabel }}
+
+
+
+
+
+
+
+
+
+ {{
+ isEmployeeLogin
+ ? employeeProfile?.[EmployeeFields.PHONENUMBER] || "-"
+ : adminProfile?.[AdministratorFields.ACCOUNT] ||
+ currentAccount
+ }}
+
+
+ {{
+ isEmployeeLogin
+ ? employeeProfile?.[EmployeeFields.EMAILADDRESS] || "-"
+ : adminProfile?.[AdministratorFields.NAME] ||
+ displayName
+ }}
+
+
+ {{
+ isEmployeeLogin
+ ? employeeProfile?.[EmployeeFields.ADDRESS] || "-"
+ : profileLoadedAt || "-"
+ }}
+
+
+ {{
+ formatDate(employeeProfile?.[EmployeeFields.HIREDATE]) ||
+ "-"
+ }}
+
+
+ {{
+ formatDate(
+ employeeProfile?.[EmployeeFields.DATEOFBIRTH],
+ ) || "-"
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ accountTypeLabel }}
+
+
+ {{ currentAccount }}
+
+
+ {{ currentUserNumber || "-" }}
+
+
+ {{ securityInfo.tokenIssuedAt }}
+
+
+ {{ securityInfo.tokenExpireAt }}
+
+
+ {{ securityInfo.tokenRemaining }}
+
+
+
+
+
+
+
+
+
+
+ {{
+ twoFactorStatus?.IsEnabled
+ ? $t("message.enabled")
+ : $t("message.disabled")
+ }}
+
+
+ {{
+ twoFactorStatus?.EnabledAt
+ ? formatDateTime(twoFactorStatus.EnabledAt)
+ : "-"
+ }}
+
+
+ {{
+ twoFactorStatus?.LastVerifiedAt
+ ? formatDateTime(twoFactorStatus.LastVerifiedAt)
+ : "-"
+ }}
+
+
+ {{ twoFactorStatus?.RemainingRecoveryCodes ?? "-" }}
+
+
+
+
+
+
+
+ {{ $t("message.generateTwoFactorSetup") }}
+
+
+
+
+
+
+
+ {{ twoFactorSetupData.ManualEntryKey }}
+
+
+
+ {{ $t("message.enableTwoFactor") }}
+
+
+
+
+
+
+ {{ $t("message.regenerateRecoveryCodes") }}
+
+
+
+
+
+ {{ recoveryCodesText }}
+
+
+ {{ $t("message.copyAllRecoveryCodes") }}
+
+
+
+
+
+ {{ $t("message.disableTwoFactor") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.changePassword") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ role[RoleFields.NAME]
+ ? `${role[RoleFields.NAME]} (${role[RoleFields.NUMBER]})`
+ : role[RoleFields.NUMBER]
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/humanresourcemanagement/EmployeeDetailView.vue b/src/views/humanresourcemanagement/EmployeeDetailView.vue
index d08f317c2c7ef3da9c8eeaafa67b03402412201d..2f75ad5cee4c606a973e63f9ed9b3236128ee5a1 100644
--- a/src/views/humanresourcemanagement/EmployeeDetailView.vue
+++ b/src/views/humanresourcemanagement/EmployeeDetailView.vue
@@ -132,6 +132,7 @@
>
+
+
+ {{ $t("message.insertWorkHistory") }}
+
+
+
+
+
+
+
{{
@@ -231,6 +255,7 @@
>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/roominformation/CheckInForm.vue b/src/views/roominformation/CheckInForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d583f97e0f36a32e523c082d0b82058e52437fc3
--- /dev/null
+++ b/src/views/roominformation/CheckInForm.vue
@@ -0,0 +1,403 @@
+
+
+
+
+ {{ room?.[RoomFields.LOCATOR] || room?.[RoomFields.NO] || "-" }}
+
+
+
+
+
+
+
+ {{ t("message.check") }}
+
+
+
+
+
+
+ {{ t("message.customerFound") }}:
+ {{ foundCustomer?.[CustomerFields.NAME] || "" }}
+ {{
+ t("message.customerNotFound")
+ }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.stayHoursHint", { hours: selectedPricingStayHours }) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/roominformation/CheckoutForm.vue b/src/views/roominformation/CheckoutForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a0bb0bcdac1444a3a1d9e60755e4bf8badd27678
--- /dev/null
+++ b/src/views/roominformation/CheckoutForm.vue
@@ -0,0 +1,644 @@
+
+
+
+
+
+
+
diff --git a/src/views/roominformation/ReserManagementView.vue b/src/views/roominformation/ReserManagementView.vue
index 0cae28c6afe64144a18afc7c72806a10bb06351a..099a09d2bfc2e8299ea597cb28dbc73c0587da83 100644
--- a/src/views/roominformation/ReserManagementView.vue
+++ b/src/views/roominformation/ReserManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -128,13 +163,19 @@
:label="reserCustomerNameLabel"
:name="ReserFields.CUSTOMERNAME"
>
-
+
-
+
-
+
@@ -181,8 +220,8 @@
{{ selectedRecord?.[ReserFields.CHANNELDESCRIPTION] }}
-
- {{ selectedRecord?.[ReserFields.ROOMNUMBER] }}
+
+ {{ selectedRecord?.[ReserFields.ROOMLOCATOR] || "-" }}
{{ formatDate(selectedRecord?.[ReserFields.STARTDATE]) }}
@@ -210,28 +249,36 @@ import {
updateReser,
deleteReser,
fetchReserTypes,
-} from "@/api/reserapi";
-import { fetchAvailableRooms } from "@/api/roomapi";
+} from "@/api/roominformation/reserapi";
+import { fetchAvailableRooms } from "@/api/roominformation/roomapi";
import {
ReserFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/reser.entity";
-import { RoomFields } from "@/entities/room.entity";
+} from "@/entities/roominformation/reser.entity";
+import {
+ RoomFields,
+ getRoomLocatorText,
+} from "@/entities/roominformation/room.entity";
import { ConstantFields } from "@/entities/constant.entity";
+import { BaseFields } from "@/entities/common.entity";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getReserFilterConfig } from "@/filters/reser.filter.config";
+import { getReserFilterConfig } from "@/filters/roominformation/reser.filter.config";
import dayjs from "dayjs";
-import generateSnowflakeId from "@/utils/snowflake";
+import generateUniqueId from "@/utils/uniquecode";
import { ReservationManagementPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const resers = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -249,7 +296,7 @@ const currentFilterParams = reactive({});
const reserFilterConfig = computed(() =>
getReserFilterConfig(t, roomOptions.value, reserTypeOptions.value),
);
-const filterValues = ref({ [ReserFields.IS_DELETED]: 0 });
+const filterValues = ref({ [BaseFields.IS_DELETED]: 0 });
const selectedRecord = ref(null);
const selectedRowKeys = ref([]);
const selectedRows = ref([]);
@@ -258,11 +305,11 @@ const displayResers = computed(() => {
let fields = reserFilterConfig.value?.fields || [];
// 如果 IsDelete 通过 ListFilter 启用但没有在 fields 中,动态追加到检查字段
if (
- filterValues[ReserFields.IS_DELETED] !== null &&
- filterValues[ReserFields.IS_DELETED] !== undefined &&
- !fields.some((f) => f.name === ReserFields.IS_DELETED)
+ filterValues[BaseFields.IS_DELETED] !== null &&
+ filterValues[BaseFields.IS_DELETED] !== undefined &&
+ !fields.some((f) => f.name === BaseFields.IS_DELETED)
) {
- fields = fields.concat({ name: ReserFields.IS_DELETED, type: "select" });
+ fields = fields.concat({ name: BaseFields.IS_DELETED, type: "select" });
}
return items;
});
@@ -270,12 +317,17 @@ const displayResers = computed(() => {
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
+
+const roomOptionMap = computed(
+ () => new Map(roomOptions.value.map((option) => [option.value, option])),
+);
const reserNoLabel = computed(() => t("message.reserNo"));
const reserCustomerNameLabel = computed(() => t("message.reserCustomerName"));
const reserPhoneNumberLabel = computed(() => t("message.reserPhoneNumber"));
const reserChannelLabel = computed(() => t("message.reserChannel"));
-const reserRoomNumberLabel = computed(() => t("message.reserRoomNumber"));
+const roomLocatorLabel = computed(() => t("message.roomLocator"));
const reserStartDateLabel = computed(() => t("message.reserStartDate"));
const reserEndDateLabel = computed(() => t("message.reserEndDate"));
@@ -287,7 +339,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[ReserFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -310,7 +362,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[ReserFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -418,13 +470,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchReserData = async () => {
+const fetchReserData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchResers({
- [ReserFields.PAGE]: pagination.current,
- [ReserFields.PAGE_SIZE]: pagination.pageSize,
- [ReserFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
resers.value = result.Items.map((item) => ({
@@ -435,10 +493,15 @@ const fetchReserData = async () => {
[ReserFields.CHANNEL]: item[ReserFields.CHANNEL],
[ReserFields.CHANNELDESCRIPTION]: item[ReserFields.CHANNELDESCRIPTION],
[ReserFields.CUSTOMERNAME]: item[ReserFields.CUSTOMERNAME],
+ [ReserFields.ROOM_ID]: item[ReserFields.ROOM_ID],
[ReserFields.ROOMNUMBER]: item[ReserFields.ROOMNUMBER],
+ [ReserFields.ROOMAREA]: item[ReserFields.ROOMAREA],
+ [ReserFields.ROOMFLOOR]: item[ReserFields.ROOMFLOOR],
+ [ReserFields.ROOMLOCATOR]:
+ item[ReserFields.ROOMLOCATOR] || item[ReserFields.ROOMNUMBER],
[ReserFields.STARTDATE]: item[ReserFields.STARTDATE],
[ReserFields.ENDDATE]: item[ReserFields.ENDDATE],
- [ReserFields.IS_DELETED]: item[ReserFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
[ReserFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -538,12 +601,23 @@ const handleFilterReset = (vals) => {
fetchReserData();
};
+const handleRoomSelectChange = (value) => {
+ const selectedOption = roomOptionMap.value.get(value);
+ form[ReserFields.ROOM_ID] = value ?? null;
+ form[ReserFields.ROOMNUMBER] = selectedOption?.roomNumber || "";
+ form[ReserFields.ROOMLOCATOR] = selectedOption?.label || "";
+};
+
const fetchSelectRooms = async () => {
try {
- const result = await fetchAvailableRooms();
+ const result = await fetchAvailableRooms({
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
+ });
roomOptions.value = result.Items.map((item) => ({
- label: item[RoomFields.NO],
- value: item[RoomFields.NO],
+ label: getRoomLocatorText(item),
+ value: item[RoomFields.ID],
+ roomNumber: item[RoomFields.NO] || "",
}));
} catch (error) {
showErrorNotification(error.message || t("message.pleaseTryAgainLater"));
@@ -568,16 +642,43 @@ onMounted(() => {
fetchReserTypeAll();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
+const buildReservationPayload = (source, overrides = {}) => ({
+ [ReserFields.ID]: source?.[ReserFields.ID] ?? null,
+ [ReserFields.ROWVERSION]: source?.[ReserFields.ROWVERSION] ?? null,
+ [ReserFields.NUMBER]: source?.[ReserFields.NUMBER] ?? "",
+ [ReserFields.CUSTOMERNAME]: source?.[ReserFields.CUSTOMERNAME] ?? "",
+ [ReserFields.PHONENUMBER]: source?.[ReserFields.PHONENUMBER] ?? "",
+ [ReserFields.CHANNEL]: source?.[ReserFields.CHANNEL] ?? "",
+ [ReserFields.ROOM_ID]: source?.[ReserFields.ROOM_ID] ?? null,
+ [ReserFields.ROOMNUMBER]: source?.[ReserFields.ROOMNUMBER] ?? "",
+ [ReserFields.STARTDATE]: source?.[ReserFields.STARTDATE]
+ ? dayjs(source[ReserFields.STARTDATE]).format("YYYY-MM-DD")
+ : null,
+ [ReserFields.ENDDATE]: source?.[ReserFields.ENDDATE]
+ ? dayjs(source[ReserFields.ENDDATE]).format("YYYY-MM-DD")
+ : null,
+ [BaseFields.IS_DELETED]: source?.[BaseFields.IS_DELETED] ?? 0,
+ ...overrides,
+});
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertReser");
- form[ReserFields.NUMBER] = generateSnowflakeId({
+ form[ReserFields.NUMBER] = generateUniqueId({
prefix: "R-",
separator: null,
});
form[ReserFields.CUSTOMERNAME] = "";
form[ReserFields.PHONENUMBER] = "";
+ form[ReserFields.ROOM_ID] = null;
form[ReserFields.ROOMNUMBER] = "";
+ form[ReserFields.ROOMLOCATOR] = "";
form[ReserFields.STARTDATE] = null;
form[ReserFields.ENDDATE] = null;
form[ReserFields.CHANNEL] = "";
@@ -585,6 +686,57 @@ const showModal = () => {
form[ReserFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: resers.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = resers.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchReserData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: resers.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ resers.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchReserData();
@@ -597,7 +749,9 @@ const editReser = (record) => {
form[ReserFields.NUMBER] = record[ReserFields.NUMBER];
form[ReserFields.CUSTOMERNAME] = record[ReserFields.CUSTOMERNAME];
form[ReserFields.PHONENUMBER] = record[ReserFields.PHONENUMBER];
+ form[ReserFields.ROOM_ID] = record[ReserFields.ROOM_ID] ?? null;
form[ReserFields.ROOMNUMBER] = record[ReserFields.ROOMNUMBER];
+ form[ReserFields.ROOMLOCATOR] = record[ReserFields.ROOMLOCATOR] || "";
form[ReserFields.STARTDATE] = record[ReserFields.STARTDATE]
? dayjs(record[ReserFields.STARTDATE])
: null;
@@ -613,15 +767,16 @@ const handleModalOk = async () => {
try {
await formRef.value.validate();
confirmLoading.value = true;
+ const payload = buildReservationPayload(form);
if (form[ReserFields.MODIFYSTATUS] === "update") {
- var response = await updateReser({ ...form });
+ var response = await updateReser(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.updateFailed"));
return;
}
showSuccessNotification(t("message.updateSuccess"));
} else {
- var response = await addReser({ ...form });
+ var response = await addReser(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.addFailed"));
return;
@@ -644,8 +799,11 @@ const handleModalCancel = () => {
const restoreReser = async (record) => {
try {
- record[ReserFields.IS_DELETED] = 0;
- const response = await updateReser(record);
+ const response = await updateReser(
+ buildReservationPayload(record, {
+ [BaseFields.IS_DELETED]: 0,
+ }),
+ );
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
} else {
diff --git a/src/views/roominformation/ReservationForm.vue b/src/views/roominformation/ReservationForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..aa39b77cbffb5dcb8c0b6fe0547b79ac1382dd41
--- /dev/null
+++ b/src/views/roominformation/ReservationForm.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/roominformation/RoomConfigView.vue b/src/views/roominformation/RoomConfigView.vue
index 984633faa31e04aaa6b5a3a8548d1eb8d6442587..c9bd38e96981508a465947d06d0aff8f7601a99e 100644
--- a/src/views/roominformation/RoomConfigView.vue
+++ b/src/views/roominformation/RoomConfigView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -129,7 +165,10 @@
-
+
@@ -149,6 +188,99 @@
:formatter="(value) => `¥ ${value}`"
/>
+ {{ pricingItemsLabel }}
+
+
+ {{ pricingItemsHint }}
+
+
+
+
+ {{ pricingItemLabel(index) }}
+
+ {{ $t("message.delete") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ addPricingItemLabel }}
+
{{ formatCurrency(selectedRecord?.[RoomConfigFields.DEPOSIT]) }}
+
+
+ {{ selectedRecord?.[RoomConfigFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[RoomConfigFields.DATA_INS_DATE])
+ }}
+
+
+
+
+ {{ selectedRecord?.[RoomConfigFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(selectedRecord?.[RoomConfigFields.DATA_CHG_DATE])
+ }}
+
+
+
+ {{ pricingItemsLabel }}
+
+
+
+
+
+
+ {{ pricingCodeLabel }}:
+ {{ item[RoomConfigPricingItemFields.CODE] || "-" }}
+
+
+ {{ pricingNameLabel }}:
+ {{ item[RoomConfigPricingItemFields.NAME] || "-" }}
+
+
+ {{ roomRentLabel }}:
+ {{ formatCurrency(item[RoomConfigPricingItemFields.RENT]) }}
+
+
+ {{ roomDepositLabel }}:
+ {{ formatCurrency(item[RoomConfigPricingItemFields.DEPOSIT]) }}
+
+
+ {{ stayHoursLabel }}:
+ {{ item[RoomConfigPricingItemFields.STAY_HOURS] ?? "-" }}
+
+
+ {{ sortLabel }}:
+ {{ item[RoomConfigPricingItemFields.SORT] ?? 0 }}
+
+
+
+
@@ -186,30 +379,38 @@ import {
addRoomType,
updateRoomType,
deleteRoomType,
-} from "@/api/roomtypeapi.js";
+} from "@/api/roominformation/roomtypeapi.js";
import {
RoomConfigFields,
+ RoomConfigPricingItemFields,
+ STANDARD_PRICING_CODE,
+ createRoomConfigPricingItem,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/roomconfig.entity";
+} from "@/entities/roominformation/roomconfig.entity";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import { RoomConfigPermissions } from "@/common/permissioncode";
import { formatCurrency } from "@/utils/index";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getRoomConfigFilterConfig } from "@/filters/roomconfig.filter.config";
+import { getRoomConfigFilterConfig } from "@/filters/roominformation/roomconfig.filter.config";
+import { exportTableToCsv, formatDateTime } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const roomconfigs = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
const modalTitle = ref("");
const confirmLoading = ref(false);
-const formRef = ref([]);
+const formRef = ref(null);
const sortedInfo = ref({ order: null, columnKey: null });
const filterValues = ref({});
const selectedRecord = ref(null);
@@ -221,12 +422,33 @@ const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const roomTypeCodeLabel = computed(() => t("message.roomTypeCode"));
const roomTypeNameLabel = computed(() => t("message.roomTypeName"));
const roomRentLabel = computed(() => t("message.roomRent"));
const roomDepositLabel = computed(() => t("message.roomDeposit"));
+const pricingItemsLabel = computed(() => t("message.pricingItems"));
+const pricingCodeLabel = computed(() => t("message.pricingCode"));
+const pricingNameLabel = computed(() => t("message.pricingName"));
+const stayHoursLabel = computed(() => t("message.stayHours"));
+const sortLabel = computed(() => t("message.sort"));
+const addPricingItemLabel = computed(() => t("message.addPricingItem"));
+const emptyPricingItemsText = computed(() => t("message.noPricingItems"));
+const pricingItemsHint = computed(() =>
+ t("message.additionalPricingItemsHint"),
+);
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const roomConfigFilterConfig = computed(() => getRoomConfigFilterConfig(t));
+const selectedAdditionalPricingItems = computed(() =>
+ normalizeRoomTypePricingItems(
+ selectedRecord.value?.[RoomConfigFields.PRICING_ITEMS],
+ true,
+ ),
+);
const pagination = reactive({
current: 1,
@@ -237,6 +459,143 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
+const pricingItemLabel = (index) => `${t("message.pricingItem")} ${index + 1}`;
+
+const normalizePricingNumber = (value, fallback = 0) => {
+ const normalizedValue = Number(value);
+ return Number.isFinite(normalizedValue) ? normalizedValue : fallback;
+};
+
+const normalizeOptionalInteger = (value) => {
+ if (value === null || value === undefined || value === "") {
+ return null;
+ }
+
+ const normalizedValue = Number(value);
+ return Number.isFinite(normalizedValue) ? normalizedValue : null;
+};
+
+const normalizeRoomTypePricingItems = (items, excludeStandard = false) => {
+ if (!Array.isArray(items)) {
+ return [];
+ }
+
+ return items
+ .filter((item) => item && typeof item === "object")
+ .filter((item) =>
+ excludeStandard
+ ? String(item?.[RoomConfigPricingItemFields.CODE] || "").trim() !==
+ STANDARD_PRICING_CODE
+ : true,
+ )
+ .map((item, index) =>
+ createRoomConfigPricingItem({
+ [RoomConfigPricingItemFields.CODE]: String(
+ item?.[RoomConfigPricingItemFields.CODE] || "",
+ ).trim(),
+ [RoomConfigPricingItemFields.NAME]: String(
+ item?.[RoomConfigPricingItemFields.NAME] || "",
+ ).trim(),
+ [RoomConfigPricingItemFields.RENT]: normalizePricingNumber(
+ item?.[RoomConfigPricingItemFields.RENT],
+ ),
+ [RoomConfigPricingItemFields.DEPOSIT]: normalizePricingNumber(
+ item?.[RoomConfigPricingItemFields.DEPOSIT],
+ ),
+ [RoomConfigPricingItemFields.STAY_HOURS]: normalizeOptionalInteger(
+ item?.[RoomConfigPricingItemFields.STAY_HOURS],
+ ),
+ [RoomConfigPricingItemFields.SORT]: normalizePricingNumber(
+ item?.[RoomConfigPricingItemFields.SORT],
+ (index + 1) * 10,
+ ),
+ [RoomConfigPricingItemFields.IS_DEFAULT]: Boolean(
+ item?.[RoomConfigPricingItemFields.IS_DEFAULT],
+ ),
+ }),
+ )
+ .sort(
+ (left, right) =>
+ normalizePricingNumber(left?.[RoomConfigPricingItemFields.SORT]) -
+ normalizePricingNumber(right?.[RoomConfigPricingItemFields.SORT]),
+ );
+};
+
+const buildRoomTypeSubmitPayload = (source) => ({
+ [RoomConfigFields.ID]: source?.[RoomConfigFields.ID] ?? null,
+ [RoomConfigFields.ROWVERSION]: source?.[RoomConfigFields.ROWVERSION] ?? null,
+ [RoomConfigFields.NO]: normalizePricingNumber(
+ source?.[RoomConfigFields.NO],
+ null,
+ ),
+ [RoomConfigFields.NAME]: String(source?.[RoomConfigFields.NAME] || "").trim(),
+ [RoomConfigFields.RENT]: normalizePricingNumber(
+ source?.[RoomConfigFields.RENT],
+ ),
+ [RoomConfigFields.DEPOSIT]: normalizePricingNumber(
+ source?.[RoomConfigFields.DEPOSIT],
+ ),
+ [BaseFields.IS_DELETED]: source?.[BaseFields.IS_DELETED] ?? 0,
+ [RoomConfigFields.PRICING_ITEMS]: normalizeRoomTypePricingItems(
+ source?.[RoomConfigFields.PRICING_ITEMS],
+ true,
+ ).map((item) => ({
+ [RoomConfigPricingItemFields.CODE]: item[RoomConfigPricingItemFields.CODE],
+ [RoomConfigPricingItemFields.NAME]: item[RoomConfigPricingItemFields.NAME],
+ [RoomConfigPricingItemFields.RENT]: item[RoomConfigPricingItemFields.RENT],
+ [RoomConfigPricingItemFields.DEPOSIT]:
+ item[RoomConfigPricingItemFields.DEPOSIT],
+ [RoomConfigPricingItemFields.STAY_HOURS]:
+ item[RoomConfigPricingItemFields.STAY_HOURS],
+ [RoomConfigPricingItemFields.SORT]: item[RoomConfigPricingItemFields.SORT],
+ })),
+});
+
+const validatePricingItems = () => {
+ const pricingItems = normalizeRoomTypePricingItems(
+ form[RoomConfigFields.PRICING_ITEMS],
+ true,
+ );
+ const pricingCodes = new Set();
+
+ for (const item of pricingItems) {
+ const pricingCode = item[RoomConfigPricingItemFields.CODE];
+ const pricingName = item[RoomConfigPricingItemFields.NAME];
+
+ if (!pricingCode) {
+ showErrorNotification(t("message.pleaseInputPricingCode"));
+ return false;
+ }
+
+ if (!pricingName) {
+ showErrorNotification(t("message.pleaseInputPricingName"));
+ return false;
+ }
+
+ if (pricingCodes.has(pricingCode)) {
+ showErrorNotification(t("message.pricingCodeDuplicate"));
+ return false;
+ }
+
+ pricingCodes.add(pricingCode);
+ }
+
+ return true;
+};
+
+const addPricingItemRow = () => {
+ form[RoomConfigFields.PRICING_ITEMS].push(
+ createRoomConfigPricingItem({
+ [RoomConfigPricingItemFields.SORT]:
+ (form[RoomConfigFields.PRICING_ITEMS].length + 1) * 10,
+ }),
+ );
+};
+
+const removePricingItem = (index) => {
+ form[RoomConfigFields.PRICING_ITEMS].splice(index, 1);
+};
+
// 行选择配置
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
@@ -245,7 +604,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[RoomConfigFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -268,7 +627,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[RoomConfigFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -335,7 +694,7 @@ const handleBatchDelete = async () => {
const deleteRequest = {
DelIds: selectedRowKeys.value, // 使用DelIds字段传递要删除的ID列表
};
- const response = await deleteRoomConfig(deleteRequest);
+ const response = await deleteRoomType(deleteRequest);
if (response && response.Success) {
showSuccessNotification(t("message.batchDeleteSuccess"));
clearSelection();
@@ -395,13 +754,19 @@ const handleFilterReset = (vals) => {
fetchRoomConfigData();
};
-const fetchRoomConfigData = async () => {
+const fetchRoomConfigData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchRoomTypes({
- [RoomConfigFields.PAGE]: pagination.current,
- [RoomConfigFields.PAGE_SIZE]: pagination.pageSize,
- [RoomConfigFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -412,6 +777,13 @@ const fetchRoomConfigData = async () => {
[RoomConfigFields.NAME]: item[RoomConfigFields.NAME],
[RoomConfigFields.RENT]: item[RoomConfigFields.RENT],
[RoomConfigFields.DEPOSIT]: item[RoomConfigFields.DEPOSIT],
+ [RoomConfigFields.DATA_INS_USER]: item[RoomConfigFields.DATA_INS_USER],
+ [RoomConfigFields.DATA_INS_DATE]: item[RoomConfigFields.DATA_INS_DATE],
+ [RoomConfigFields.DATA_CHG_USER]: item[RoomConfigFields.DATA_CHG_USER],
+ [RoomConfigFields.DATA_CHG_DATE]: item[RoomConfigFields.DATA_CHG_DATE],
+ [RoomConfigFields.PRICING_ITEMS]: normalizeRoomTypePricingItems(
+ item[RoomConfigFields.PRICING_ITEMS],
+ ),
[RoomConfigFields.IS_DELETED]: item[RoomConfigFields.IS_DELETED] ?? 0,
[RoomConfigFields.SELECTED]: false,
}));
@@ -428,17 +800,75 @@ onMounted(() => {
fetchRoomConfigData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertRoomConfig");
form[RoomConfigFields.NO] = null;
form[RoomConfigFields.NAME] = "";
form[RoomConfigFields.RENT] = 0;
form[RoomConfigFields.DEPOSIT] = 0;
+ form[RoomConfigFields.PRICING_ITEMS] = [];
form[RoomConfigFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: roomconfigs.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = roomconfigs.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchRoomConfigData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: roomconfigs.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ roomconfigs.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchRoomConfigData();
@@ -448,10 +878,15 @@ const editRoomConfig = (record) => {
modalVisible.value = true;
modalTitle.value = t("message.updateRoomConfig");
form[RoomConfigFields.ID] = record[RoomConfigFields.ID];
+ form[RoomConfigFields.ROWVERSION] = record[RoomConfigFields.ROWVERSION];
form[RoomConfigFields.NO] = record[RoomConfigFields.NO];
form[RoomConfigFields.NAME] = record[RoomConfigFields.NAME];
form[RoomConfigFields.RENT] = record[RoomConfigFields.RENT];
form[RoomConfigFields.DEPOSIT] = record[RoomConfigFields.DEPOSIT];
+ form[RoomConfigFields.PRICING_ITEMS] = normalizeRoomTypePricingItems(
+ record[RoomConfigFields.PRICING_ITEMS],
+ true,
+ );
form[RoomConfigFields.MODIFYSTATUS] = "update";
};
@@ -459,16 +894,20 @@ const editRoomConfig = (record) => {
const handleModalOk = async () => {
try {
await formRef.value.validate();
+ if (!validatePricingItems()) {
+ return;
+ }
confirmLoading.value = true;
+ const payload = buildRoomTypeSubmitPayload(form);
if (form[RoomConfigFields.MODIFYSTATUS] === "update") {
- var response = await updateRoomType({ ...form });
+ var response = await updateRoomType(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.updateFailed"));
return;
}
showSuccessNotification(t("message.updateSuccess"));
} else {
- var response = await addRoomType({ ...form });
+ var response = await addRoomType(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.addFailed"));
return;
@@ -491,8 +930,12 @@ const handleModalCancel = () => {
const restoreRoomConfig = async (record) => {
try {
- record[RoomConfigFields.IS_DELETED] = 0;
- const response = await updateRoomType(record);
+ const response = await updateRoomType(
+ buildRoomTypeSubmitPayload({
+ ...record,
+ [BaseFields.IS_DELETED]: 0,
+ }),
+ );
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
} else {
@@ -515,3 +958,26 @@ const handleSorterChange = (pagination, filters, sorter) => {
sortedInfo.value = sorter;
};
+
+
diff --git a/src/views/roominformation/RoomGoodsConsumptionDialog.vue b/src/views/roominformation/RoomGoodsConsumptionDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ea2aa13f5f828d50cd7c145b3790634daaaa386d
--- /dev/null
+++ b/src/views/roominformation/RoomGoodsConsumptionDialog.vue
@@ -0,0 +1,593 @@
+
+
+
+
+
+
+ 商品信息
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t("message.insertSpend") }}
+
+
+ {{ t("message.recallSpend") }}
+
+
+
+
+ 房间消费列表
+
+
+
+
+
+
+
+
+
diff --git a/src/views/roominformation/RoomManagementView.vue b/src/views/roominformation/RoomManagementView.vue
index b06c0e21d348260ba1545c3615cc87d42769e17a..ab355ed72a7431609c9e42327861ac9e8997db6f 100644
--- a/src/views/roominformation/RoomManagementView.vue
+++ b/src/views/roominformation/RoomManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
+
+ {{ record[RoomFields.PRICING_NAME] || t("message.standardPrice") }}
+
+
+ {{
+ formatCurrency(
+ record[RoomFields.EFFECTIVE_RENT] ?? record[RoomFields.RENT],
+ )
+ }}
+
+
+ {{
+ formatCurrency(
+ record[RoomFields.EFFECTIVE_DEPOSIT] ??
+ record[RoomFields.DEPOSIT],
+ )
+ }}
+
@@ -116,12 +167,31 @@
:title="modalTitle"
:width="'60%'"
:confirm-loading="confirmLoading"
+ :ok-text="$t('message.save')"
+ :ok-button-props="{ disabled: !isFormValid }"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
-
+
+
+
+
+
+
+
-
-
+
+
@@ -165,9 +238,18 @@
:footer="null"
>
+
+ {{ selectedRecord?.[RoomFields.LOCATOR] || "-" }}
+
{{ selectedRecord?.[RoomFields.NO] }}
+
+ {{ selectedRecord?.[RoomFields.AREA] || "-" }}
+
+
+ {{ selectedRecord?.[RoomFields.FLOOR] ?? "-" }}
+
{{ selectedRecord?.[RoomFields.NAME] }}
@@ -176,15 +258,63 @@
{{ selectedRecord?.[RoomFields.STATE] }}
-
- {{ selectedRecord?.[RoomFields.RENT] }}
+
+ {{
+ selectedRecord?.[RoomFields.PRICING_NAME] ||
+ t("message.standardPrice")
+ }}
+
+
+ {{
+ formatCurrency(
+ selectedRecord?.[RoomFields.EFFECTIVE_RENT] ??
+ selectedRecord?.[RoomFields.RENT],
+ )
+ }}
+
+
+ {{
+ formatCurrency(
+ selectedRecord?.[RoomFields.EFFECTIVE_DEPOSIT] ??
+ selectedRecord?.[RoomFields.DEPOSIT],
+ )
+ }}
+
+
+ {{
+ formatCurrency(
+ selectedRecord?.[RoomFields.STANDARD_RENT] ??
+ selectedRecord?.[RoomFields.RENT],
+ )
+ }}
-
- {{ selectedRecord?.[RoomFields.DEPOSIT] }}
+
+ {{
+ formatCurrency(
+ selectedRecord?.[RoomFields.STANDARD_DEPOSIT] ??
+ selectedRecord?.[RoomFields.DEPOSIT],
+ )
+ }}
-
- {{ selectedRecord?.[RoomFields.POSITION] }}
+
+ {{ selectedRecord?.[RoomFields.LOCATION] || "-" }}
+
+
+ {{ selectedRecord?.[RoomFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[RoomFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[RoomFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[RoomFields.DATA_CHG_DATE]) }}
+
+
@@ -194,27 +324,41 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
-import { fetchRooms, addRoom, updateRoom, deleteRoom } from "@/api/roomapi";
+import {
+ formatCurrency,
+ showErrorNotification,
+ showSuccessNotification,
+} from "@/utils/index";
+import {
+ fetchRooms,
+ addRoom,
+ updateRoom,
+ deleteRoom,
+} from "@/api/roominformation/roomapi";
import {
RoomFields,
initialFormValues,
getColumns,
getFormRules,
RoomStateColors,
-} from "@/entities/room.entity";
-import { fetchRoomTypes } from "@/api/roomtypeapi.js";
-import { fetchRoomStates } from "@/api/roomstateapi.js";
+} from "@/entities/roominformation/room.entity";
+import { fetchRoomTypes } from "@/api/roominformation/roomtypeapi.js";
+import { fetchRoomStates } from "@/api/roominformation/roomstateapi.js";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import { RoomManagementPermissions } from "@/common/permissioncode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getRoomFilterConfig } from "@/filters/room.filter.config";
+import { getRoomFilterConfig } from "@/filters/roominformation/room.filter.config";
+import { exportTableToCsv, formatDateTime } from "@/utils/index";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const rooms = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -234,13 +378,28 @@ const currentFilterParams = reactive({});
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const roomNoLabel = computed(() => t("message.roomNo"));
+const roomAreaLabel = computed(() => t("message.roomArea"));
+const roomFloorLabel = computed(() => t("message.roomFloor"));
+const roomLocatorLabel = computed(() => t("message.roomLocator"));
const roomTypeLabel = computed(() => t("message.roomType"));
const roomStateLabel = computed(() => t("message.roomState"));
const roomRentLabel = computed(() => t("message.roomRent"));
const roomDepositLabel = computed(() => t("message.roomDeposit"));
-const roomPositionLabel = computed(() => t("message.roomPosition"));
+const roomLocationLabel = computed(() => t("message.roomLocation"));
+const currentPricingLabel = computed(() => t("message.currentPricing"));
+const currentRoomRentLabel = computed(() => t("message.currentRoomRent"));
+const currentRoomDepositLabel = computed(() => t("message.currentRoomDeposit"));
+const standardRoomRentLabel = computed(() => t("message.standardRoomRent"));
+const standardRoomDepositLabel = computed(() =>
+ t("message.standardRoomDeposit"),
+);
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const getTagColor = (state) => RoomStateColors[state] || "#888";
@@ -269,7 +428,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[RoomFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -292,7 +451,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[RoomFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -387,6 +546,35 @@ const columns = computed(() => {
];
});
+const toNullableNumber = (value) => {
+ if (value === null || value === undefined || value === "") {
+ return null;
+ }
+
+ const normalizedValue = Number(value);
+ return Number.isFinite(normalizedValue) ? normalizedValue : null;
+};
+
+const toNumber = (value, fallback = 0) => {
+ const normalizedValue = Number(value);
+ return Number.isFinite(normalizedValue) ? normalizedValue : fallback;
+};
+
+const buildRoomSubmitPayload = (source, overrides = {}) => ({
+ [RoomFields.ID]: source?.[RoomFields.ID] ?? null,
+ [RoomFields.ROWVERSION]: source?.[RoomFields.ROWVERSION] ?? null,
+ [RoomFields.NO]: source?.[RoomFields.NO] ?? "",
+ [RoomFields.AREA]: source?.[RoomFields.AREA] ?? "",
+ [RoomFields.FLOOR]: toNullableNumber(source?.[RoomFields.FLOOR]),
+ [RoomFields.TYPE]: toNullableNumber(source?.[RoomFields.TYPE]),
+ [RoomFields.STATE_ID]: toNullableNumber(source?.[RoomFields.STATE_ID]),
+ [RoomFields.RENT]: toNumber(source?.[RoomFields.RENT]),
+ [RoomFields.DEPOSIT]: toNumber(source?.[RoomFields.DEPOSIT]),
+ [RoomFields.LOCATION]: source?.[RoomFields.LOCATION] ?? "",
+ [BaseFields.IS_DELETED]: source?.[BaseFields.IS_DELETED] ?? 0,
+ ...overrides,
+});
+
const handleFilterSearch = (values) => {
Object.keys(currentFilterParams).forEach(
(k) => delete currentFilterParams[k],
@@ -419,28 +607,45 @@ const handleFilterReset = (vals) => {
fetchRoomData();
};
-const fetchRoomData = async () => {
+const fetchRoomData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchRooms({
- [RoomFields.PAGE]: pagination.current,
- [RoomFields.PAGE_SIZE]: pagination.pageSize,
- [RoomFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
rooms.value = result.Items.map((item) => ({
[RoomFields.ID]: item[RoomFields.ID],
[RoomFields.ROWVERSION]: item[RoomFields.ROWVERSION],
[RoomFields.NO]: item[RoomFields.NO],
+ [RoomFields.AREA]: item[RoomFields.AREA],
+ [RoomFields.FLOOR]: item[RoomFields.FLOOR],
+ [RoomFields.LOCATOR]: item[RoomFields.LOCATOR],
+ [RoomFields.LOCATION]: item[RoomFields.LOCATION],
[RoomFields.NAME]: item[RoomFields.NAME],
[RoomFields.TYPE]: item[RoomFields.TYPE],
- [RoomFields.TYPENAME]: item[RoomFields.TYPENAME],
[RoomFields.STATE]: item[RoomFields.STATE],
[RoomFields.STATE_ID]: item[RoomFields.STATE_ID],
[RoomFields.RENT]: item[RoomFields.RENT],
[RoomFields.DEPOSIT]: item[RoomFields.DEPOSIT],
- [RoomFields.POSITION]: item[RoomFields.POSITION],
- [RoomFields.IS_DELETED]: item[RoomFields.IS_DELETED] ?? 0,
+ [RoomFields.STANDARD_RENT]: item[RoomFields.STANDARD_RENT],
+ [RoomFields.STANDARD_DEPOSIT]: item[RoomFields.STANDARD_DEPOSIT],
+ [RoomFields.EFFECTIVE_RENT]: item[RoomFields.EFFECTIVE_RENT],
+ [RoomFields.EFFECTIVE_DEPOSIT]: item[RoomFields.EFFECTIVE_DEPOSIT],
+ [RoomFields.PRICING_NAME]: item[RoomFields.PRICING_NAME],
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [RoomFields.DATA_INS_USER]: item[RoomFields.DATA_INS_USER],
+ [RoomFields.DATA_INS_DATE]: item[RoomFields.DATA_INS_DATE],
+ [RoomFields.DATA_CHG_USER]: item[RoomFields.DATA_CHG_USER],
+ [RoomFields.DATA_CHG_DATE]: item[RoomFields.DATA_CHG_DATE],
[RoomFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -454,8 +659,8 @@ const fetchRoomData = async () => {
const fetchSelectRoomTypes = async () => {
try {
const result = await fetchRoomTypes({
- [RoomFields.IS_DELETED]: 0,
- [RoomFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
roomTypeOptions.value = result.Items.map((item) => ({
label: item.RoomTypeName,
@@ -485,19 +690,78 @@ onMounted(() => {
fetchSelectRoomStates();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertRoom");
form[RoomFields.NO] = null;
+ form[RoomFields.AREA] = "";
+ form[RoomFields.FLOOR] = null;
form[RoomFields.TYPE] = null;
form[RoomFields.RENT] = 0;
form[RoomFields.DEPOSIT] = 0;
form[RoomFields.STATE_ID] = null;
- form[RoomFields.POSITION] = "";
+ form[RoomFields.LOCATION] = "";
form[RoomFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: rooms.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = rooms.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchRoomData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: rooms.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ rooms.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchRoomData();
@@ -507,12 +771,20 @@ const editRoom = (record) => {
modalVisible.value = true;
modalTitle.value = t("message.updateRoom");
form[RoomFields.ID] = record[RoomFields.ID];
+ form[RoomFields.ROWVERSION] = record[RoomFields.ROWVERSION];
form[RoomFields.NO] = record[RoomFields.NO];
+ form[RoomFields.AREA] = record[RoomFields.AREA];
+ form[RoomFields.FLOOR] = record[RoomFields.FLOOR];
form[RoomFields.TYPE] = record[RoomFields.TYPE];
form[RoomFields.STATE_ID] = record[RoomFields.STATE_ID];
form[RoomFields.RENT] = record[RoomFields.RENT];
form[RoomFields.DEPOSIT] = record[RoomFields.DEPOSIT];
- form[RoomFields.POSITION] = record[RoomFields.POSITION];
+ form[RoomFields.STANDARD_RENT] = record[RoomFields.STANDARD_RENT];
+ form[RoomFields.STANDARD_DEPOSIT] = record[RoomFields.STANDARD_DEPOSIT];
+ form[RoomFields.LOCATION] = record[RoomFields.LOCATION];
+ form[RoomFields.EFFECTIVE_RENT] = record[RoomFields.EFFECTIVE_RENT];
+ form[RoomFields.EFFECTIVE_DEPOSIT] = record[RoomFields.EFFECTIVE_DEPOSIT];
+ form[RoomFields.PRICING_NAME] = record[RoomFields.PRICING_NAME];
form[RoomFields.MODIFYSTATUS] = "update";
};
@@ -521,15 +793,16 @@ const handleModalOk = async () => {
try {
await formRef.value.validate();
confirmLoading.value = true;
+ const payload = buildRoomSubmitPayload(form);
if (form[RoomFields.MODIFYSTATUS] === "update") {
- var response = await updateRoom({ ...form });
+ var response = await updateRoom(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.updateFailed"));
return;
}
showSuccessNotification(t("message.updateSuccess"));
} else {
- var response = await addRoom({ ...form });
+ var response = await addRoom(payload);
if (response && response.Success !== true) {
showErrorNotification(t("message.addFailed"));
return;
@@ -552,8 +825,11 @@ const handleModalCancel = () => {
const restoreRoom = async (record) => {
try {
- record[RoomFields.IS_DELETED] = 0;
- const response = await updateRoom(record);
+ const response = await updateRoom(
+ buildRoomSubmitPayload(record, {
+ [BaseFields.IS_DELETED]: 0,
+ }),
+ );
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
} else {
diff --git a/src/views/roominformation/RoomMapView.vue b/src/views/roominformation/RoomMapView.vue
index 600ea339f5772a972ecd4a3c8a38e05ca687bfc9..e8a11b8ef26bce8e5363f176b50dd4ba11fb4fdd 100644
--- a/src/views/roominformation/RoomMapView.vue
+++ b/src/views/roominformation/RoomMapView.vue
@@ -1,74 +1,312 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
-
-
-
{{ level }}
-
-
-
-
-
-
- {{ $t("message.checkInTime") }}:
- {{ formatDate(room[RoomFields.CHECK_IN_TIME]) }}
-
-
- {{ $t("message.checkOutTime") }}:
- {{ formatDate(room[RoomFields.CHECK_OUT_TIME]) }}
-
-
- {{ $t("message.roomRent") }}:
- {{ formatCurrency(room[RoomFields.RENT]) }}
-
-
- {{ $t("message.roomDeposit") }}:
- {{ formatCurrency(room[RoomFields.DEPOSIT]) }}
-
-
-
+
+
+
+
+
+
{{ $t("message.roomArea") }}
+
+
+
+
{{ $t("message.roomFloor") }}
+
+
+
+
{{ $t("message.roomType") }}
+
+
+
+
{{ $t("message.roomState") }}
+
+
+ {{ $t("message.reset") }}
+
+
+
+
+
{{ $t("message.roomTypeInventory") }}
+
+
+ {{ item.type }}: {{ item.count }}
+
+
+ {{ $t("message.totalRooms") }}: {{ filteredRoomTotal }}
+
+
+
+
+
-
-
+
+
+
+
+
-
+
{{ $t("message.reserve") }}
-
+
{{ $t("message.checkIn") }}
-
+
{{ $t("message.transferRoom") }}
-
+
{{ $t("message.checkOut") }}
-
+
{{ $t("message.changeRoomState") }}
-
+
{{ $t("message.viewCustomerInfo") }}
+
+
+ {{ $t("message.roomGoodsConsumption") }}
+
+
+
+
+
+
+
+
+ {{ viewCustomerData?.[CustomerFields.NUMBER] || "-" }}
+
+
+ {{ viewCustomerData?.[CustomerFields.NAME] || "-" }}
+
+
+ {{
+ viewCustomerData?.[CustomerFields.GENDER] === Gender.FEMALE
+ ? $t("message.female")
+ : viewCustomerData?.[CustomerFields.GENDER] === Gender.MALE
+ ? $t("message.male")
+ : "-"
+ }}
+
+
+ {{
+ viewCustomerData?.[CustomerFields.BIRTH_DATE]
+ ? formatDate(viewCustomerData?.[CustomerFields.BIRTH_DATE])
+ : "-"
+ }}
+
+
+ {{ viewCustomerData?.[CustomerFields.TYPE_NAME] || "-" }}
+
+
+ {{ viewCustomerData?.[CustomerFields.PASSPORTNAME] || "-" }}
+
+
+ {{ viewCustomerData?.[CustomerFields.ID_NUMBER] || "-" }}
+
+
+ {{ viewCustomerData?.[CustomerFields.PHONE] || "-" }}
+
+
+ {{ viewCustomerData?.[CustomerFields.ADDRESS] || "-" }}
+
+
+
+
+
diff --git a/src/views/roominformation/TransferRoomForm.vue b/src/views/roominformation/TransferRoomForm.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e38a5b72f58325b7b3aed9f657a12a8a64f2fab5
--- /dev/null
+++ b/src/views/roominformation/TransferRoomForm.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/supervision/SupervisionView.vue b/src/views/supervision/SupervisionView.vue
index 202f1663e143e36377af9fd218b03095a99bf3dc..06228e6ef4ea43301f035e02ff92329d41105962 100644
--- a/src/views/supervision/SupervisionView.vue
+++ b/src/views/supervision/SupervisionView.vue
@@ -5,6 +5,38 @@
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
+
+ {{ formatDate(record[SupervisionFields.CHECK_TIME]) }}
+
@@ -110,6 +146,8 @@
:title="modalTitle"
:width="'60%'"
:confirm-loading="confirmLoading"
+ :ok-text="$t('message.save')"
+ :ok-button-props="{ disabled: !isFormValid }"
@ok="handleModalOk"
@cancel="handleModalCancel"
>
@@ -117,6 +155,16 @@
{{ form[SupervisionFields.CHECK_NO] }}
+
+
+
-
+
-
+
@@ -153,13 +208,19 @@
:label="checkPersonLabel"
:name="SupervisionFields.CHECK_PERSON"
>
-
+
-
+
@@ -174,6 +235,9 @@
{{ selectedRecord?.[SupervisionFields.CHECK_NO] }}
+
+ {{ formatDate(selectedRecord?.[SupervisionFields.CHECK_TIME]) }}
+
{{ selectedRecord?.[SupervisionFields.CHECK_CLUB_NAME] }}
@@ -192,6 +256,22 @@
{{ selectedRecord?.[SupervisionFields.CHECK_ADVICE] }}
+
+
+ {{ selectedRecord?.[SupervisionFields.DATA_INS_USER] }}
+
+
+ {{ formatDate(selectedRecord?.[SupervisionFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[SupervisionFields.DATA_CHG_USER] }}
+
+
+ {{ formatDate(selectedRecord?.[SupervisionFields.DATA_CHG_DATE]) }}
+
+
@@ -206,31 +286,37 @@ import {
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/supervision.entity";
+} from "@/entities/supervision/supervision.entity";
import {
fetchSupervisionInfos,
addSupervisionInfo,
updateSupervisionInfo,
deleteSupervisionInfo,
-} from "@/api/supervisioninfoapi";
-import { fetchDepartments } from "@/api/departmentapi";
-import { DepartmentFields } from "@/entities/department.entity";
+} from "@/api/supervision/supervisioninfoapi";
+import { fetchDepartments } from "@/api/base/departmentapi";
+import { DepartmentFields } from "@/entities/base/department.entity";
+import { BaseFields } from "@/entities/common.entity";
+import dayjs from "dayjs";
import {
formatDate,
+ formatDateTime,
showErrorNotification,
showSuccessNotification,
} from "@/utils/index";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getSupervisionFilterConfig } from "@/filters/supervision.filter.config";
+import { getSupervisionFilterConfig } from "@/filters/supervision/supervision.filter.config";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import generateUniqueId from "@/utils/uniquecode";
import { SupervisionPermissions } from "@/common/permissioncode";
+import { exportTableToCsv } from "@/utils/index";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const supervisioninfos = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -248,6 +334,19 @@ const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const isFormValid = computed(() => {
+ return !!(
+ form[SupervisionFields.CHECK_CLUB] &&
+ form[SupervisionFields.CHECK_PROGRES]?.trim() &&
+ form[SupervisionFields.CHECK_CASH]?.trim() &&
+ form[SupervisionFields.CHECK_SCORE] !== null &&
+ form[SupervisionFields.CHECK_SCORE] !== undefined &&
+ form[SupervisionFields.CHECK_PERSON]?.trim() &&
+ form[SupervisionFields.CHECK_ADVICE]?.trim() &&
+ form[SupervisionFields.CHECK_TIME]
+ );
+});
+
const checkNoLabel = computed(() => t("message.checkNo"));
const checkDepartmentLabel = computed(() => t("message.checkDepartment"));
const checkProgresLabel = computed(() => t("message.checkProgres"));
@@ -255,6 +354,11 @@ const checkCashLabel = computed(() => t("message.checkCash"));
const checkScoreLabel = computed(() => t("message.checkScore"));
const checkPersonLabel = computed(() => t("message.checkPerson"));
const checkAdviceLabel = computed(() => t("message.checkAdvice"));
+const checkDateLabel = computed(() => t("message.checkDate"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
const currentFilterParams = reactive({});
const supervisionFilterConfig = computed(() =>
@@ -269,7 +373,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[SupervisionFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -292,7 +396,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[SupervisionFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -392,7 +496,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...supervisionFilterConfig.value?.fields.map((f) => f.name),
- SupervisionFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -407,7 +511,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === SupervisionFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -483,13 +587,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchSupervisionInfoData = async () => {
+const fetchSupervisionInfoData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchSupervisionInfos({
- [SupervisionFields.PAGE]: pagination.current,
- [SupervisionFields.PAGE_SIZE]: pagination.pageSize,
- [SupervisionFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -506,7 +616,16 @@ const fetchSupervisionInfoData = async () => {
[SupervisionFields.CHECK_SCORE]: item[SupervisionFields.CHECK_SCORE],
[SupervisionFields.CHECK_PERSON]: item[SupervisionFields.CHECK_PERSON],
[SupervisionFields.CHECK_ADVICE]: item[SupervisionFields.CHECK_ADVICE],
+ [SupervisionFields.CHECK_TIME]: item[SupervisionFields.CHECK_TIME],
[SupervisionFields.IS_DELETED]: item[SupervisionFields.IS_DELETED] ?? 0,
+ [SupervisionFields.DATA_INS_USER]:
+ item[SupervisionFields.DATA_INS_USER],
+ [SupervisionFields.DATA_INS_DATE]:
+ item[SupervisionFields.DATA_INS_DATE],
+ [SupervisionFields.DATA_CHG_USER]:
+ item[SupervisionFields.DATA_CHG_USER],
+ [SupervisionFields.DATA_CHG_DATE]:
+ item[SupervisionFields.DATA_CHG_DATE],
[SupervisionFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -523,7 +642,7 @@ const fetchSupervisionInfoData = async () => {
const fetchSelectDepartments = async () => {
try {
const result = await fetchDepartments({
- [SupervisionFields.IS_DELETED]: 0,
+ [BaseFields.IS_DELETED]: 0,
});
departmentOptions.value = result.Items.map((item) => ({
label: item[DepartmentFields.NAME],
@@ -539,17 +658,74 @@ onMounted(() => {
fetchSelectDepartments();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertSupervisionInfo");
Object.assign(form, initialFormValues);
- form[SupervisionFields.CHECK_NO] = generateSnowflakeId({
+ form[SupervisionFields.CHECK_NO] = generateUniqueId({
prefix: "SI-",
separator: null,
});
form[SupervisionFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: supervisioninfos.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = supervisioninfos.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchSupervisionInfoData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: supervisioninfos.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ supervisioninfos.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchSupervisionInfoData();
@@ -567,7 +743,10 @@ const editSupervisionInfo = (record) => {
form[SupervisionFields.CHECK_SCORE] = record[SupervisionFields.CHECK_SCORE];
form[SupervisionFields.CHECK_PERSON] = record[SupervisionFields.CHECK_PERSON];
form[SupervisionFields.CHECK_ADVICE] = record[SupervisionFields.CHECK_ADVICE];
- form[SupervisionFields.IS_DELETED] = record[SupervisionFields.IS_DELETED];
+ form[SupervisionFields.CHECK_TIME] = record[SupervisionFields.CHECK_TIME]
+ ? dayjs(record[SupervisionFields.CHECK_TIME]).format("YYYY-MM-DD")
+ : null;
+ form[BaseFields.IS_DELETED] = record[BaseFields.IS_DELETED];
form[SupervisionFields.MODIFYSTATUS] = "update";
};
@@ -598,7 +777,8 @@ const handleModalOk = async () => {
clearSelection(); // 重置选择
fetchSupervisionInfoData();
} catch (error) {
- showErrorNotification(error.message || t("message.pleaseTryAgainLater"));
+ // 表单验证失败,不显示错误提示
+ return;
} finally {
confirmLoading.value = false;
}
@@ -610,7 +790,7 @@ const handleModalCancel = () => {
const restoreSupervisionInfo = async (record) => {
try {
- record[SupervisionFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateSupervisionInfo(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/systemmanagement/AdminTypeManagementView.vue b/src/views/systemmanagement/AdminTypeManagementView.vue
index e5948ba47f621aed3a75b6c8acfdf058f42c16f2..1a5658942c1bfaf102673a46af60ae8224ee239d 100644
--- a/src/views/systemmanagement/AdminTypeManagementView.vue
+++ b/src/views/systemmanagement/AdminTypeManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -127,7 +162,10 @@
:label="typeNameLabel"
:name="AdministratorTypeFields.NAME"
>
-
+
@@ -145,6 +183,34 @@
{{ selectedRecord?.[AdministratorTypeFields.NAME] }}
+
+
+ {{ selectedRecord?.[AdministratorTypeFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[AdministratorTypeFields.DATA_INS_DATE],
+ )
+ }}
+
+
+
+
+ {{ selectedRecord?.[AdministratorTypeFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[AdministratorTypeFields.DATA_CHG_DATE],
+ )
+ }}
+
+
@@ -154,30 +220,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ exportTableToCsv,
+ formatDateTime,
+} from "@/utils/index";
import {
fetchAdminTypes,
addAdminType,
updateAdminType,
deleteAdminType,
-} from "@/api/administratortypeapi";
+} from "@/api/systemmanagement/administratortypeapi";
import {
AdministratorTypeFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/administratortype.entity";
+} from "@/entities/systemmanagement/administratortype.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getAdminTypeFilterConfig } from "@/filters/administatortype.filter.config";
+import { getAdminTypeFilterConfig } from "@/filters/systemmanagement/administatortype.filter.config";
import { AdminTypeManagementPermissions } from "@/common/permissioncode";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const admintypes = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -195,9 +270,14 @@ const sortedInfo = ref({ order: null, columnKey: null });
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const typeNoLabel = computed(() => t("message.adminTypeNumber"));
const typeNameLabel = computed(() => t("message.adminTypeName"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
// 行选择配置
const rowSelection = computed(() => ({
@@ -207,7 +287,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[AdministratorTypeFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -230,7 +310,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[AdministratorTypeFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -333,7 +413,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...adminTypeFilterConfig.value?.fields.map((f) => f.name),
- AdministratorTypeFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -348,7 +428,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === AdministratorTypeFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -420,13 +500,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchAdminTypeData = async () => {
+const fetchAdminTypeData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchAdminTypes({
- [AdministratorTypeFields.PAGE]: pagination.current,
- [AdministratorTypeFields.PAGE_SIZE]: pagination.pageSize,
- [AdministratorTypeFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -436,8 +522,15 @@ const fetchAdminTypeData = async () => {
item[AdministratorTypeFields.ROWVERSION],
[AdministratorTypeFields.NUMBER]: item[AdministratorTypeFields.NUMBER],
[AdministratorTypeFields.NAME]: item[AdministratorTypeFields.NAME],
- [AdministratorTypeFields.IS_DELETED]:
- item[AdministratorTypeFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [AdministratorTypeFields.DATA_INS_USER]:
+ item[AdministratorTypeFields.DATA_INS_USER],
+ [AdministratorTypeFields.DATA_INS_DATE]:
+ item[AdministratorTypeFields.DATA_INS_DATE],
+ [AdministratorTypeFields.DATA_CHG_USER]:
+ item[AdministratorTypeFields.DATA_CHG_USER],
+ [AdministratorTypeFields.DATA_CHG_DATE]:
+ item[AdministratorTypeFields.DATA_CHG_DATE],
[AdministratorTypeFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -453,10 +546,16 @@ onMounted(() => {
fetchAdminTypeData();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertAdminType");
- form[AdministratorTypeFields.NUMBER] = generateSnowflakeId({
+ form[AdministratorTypeFields.NUMBER] = generateUniqueId({
prefix: "AT-",
separator: null,
});
@@ -464,6 +563,57 @@ const showModal = () => {
form[AdministratorTypeFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: admintypes.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = admintypes.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchAdminTypeData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: admintypes.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ admintypes.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchAdminTypeData();
@@ -513,14 +663,14 @@ const handleModalCancel = () => {
const restoreAdminType = async (record) => {
try {
- record[AdministratorTypeFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateAdminType(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
} else {
showErrorNotification(response.Message || t("message.operationFailed"));
}
- fetchDepartmentData();
+ fetchAdminTypeData();
} catch (error) {
showErrorNotification(error.message || t("message.pleaseTryAgainLater"));
}
diff --git a/src/views/systemmanagement/AdministratorManagementView.vue b/src/views/systemmanagement/AdministratorManagementView.vue
index ef2be54f6616961036c325d04b6765396e94e4f4..2840318131c8b681e284a935d3547f7beefef5a4 100644
--- a/src/views/systemmanagement/AdministratorManagementView.vue
+++ b/src/views/systemmanagement/AdministratorManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -208,6 +243,30 @@
: t("message.no")
}}
+
+
+ {{ selectedRecord?.[AdministratorFields.DATA_INS_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[AdministratorFields.DATA_INS_DATE],
+ )
+ }}
+
+
+
+
+ {{ selectedRecord?.[AdministratorFields.DATA_CHG_USER] }}
+
+
+ {{
+ formatDateTime(
+ selectedRecord?.[AdministratorFields.DATA_CHG_DATE],
+ )
+ }}
+
+
@@ -217,26 +276,33 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ formatDateTime,
+ exportTableToCsv,
+} from "@/utils/index";
import {
fetchAdmins,
addAdmin,
updateAdmin,
deleteAdmin,
-} from "@/api/administratorapi";
-import { fetchAdminTypes } from "@/api/administratortypeapi";
-import { AdministratorTypeFields } from "@/entities/administratortype.entity";
+} from "@/api/systemmanagement/administratorapi";
+import { fetchAdminTypes } from "@/api/systemmanagement/administratortypeapi";
+import { AdministratorTypeFields } from "@/entities/systemmanagement/administratortype.entity";
+import { BaseFields } from "@/entities/common.entity";
import {
AdministratorFields,
SuperAdmin,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/administrator.entity";
+} from "@/entities/systemmanagement/administrator.entity";
import { useI18n } from "vue-i18n";
-import generateSnowflakeId from "@/utils/snowflake";
+import useModalForm from "@/composables/useModalForm";
+import generateUniqueId from "@/utils/uniquecode";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getAdministratorFilterConfig } from "@/filters/administrator.filter.config";
+import { getAdministratorFilterConfig } from "@/filters/systemmanagement/administrator.filter.config";
import { AdministratorManagementPermissions } from "@/common/permissioncode";
const { t } = useI18n();
@@ -245,6 +311,8 @@ const router = useRouter();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const admins = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -266,6 +334,7 @@ const administratorFilterConfig = computed(() =>
const form = reactive({ ...initialFormValues });
const rules = computed(() => getFormRules(t, form.modifystatus === "update"));
+const { isFormValid } = useModalForm(form, rules);
const adminNumberLabel = computed(() => t("message.adminNumber"));
const adminNameLabel = computed(() => t("message.adminName"));
@@ -273,6 +342,10 @@ const adminAccountLabel = computed(() => t("message.adminAccount"));
const adminPasswordLabel = computed(() => t("message.adminPassword"));
const adminTypeLabel = computed(() => t("message.adminType"));
const isSuperAdminLabel = computed(() => t("message.isSuperAdminFlag"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
// 行选择配置
const rowSelection = computed(() => ({
@@ -282,7 +355,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[AdministratorFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -305,7 +378,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[AdministratorFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -392,7 +465,7 @@ const columns = computed(() => {
{
title: t("message.action"),
key: "action",
- width: 100,
+ width: 150,
fixed: "left",
align: "center",
},
@@ -405,7 +478,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...administratorFilterConfig.value?.fields.map((f) => f.name),
- AdministratorFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -420,7 +493,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === AdministratorFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -496,13 +569,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchAdministratorData = async () => {
+const fetchAdministratorData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchAdmins({
- [AdministratorFields.PAGE]: pagination.current,
- [AdministratorFields.PAGE_SIZE]: pagination.pageSize,
- [AdministratorFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -519,8 +598,15 @@ const fetchAdministratorData = async () => {
item[AdministratorFields.ISSUPERADMIN],
[AdministratorFields.ISSUPERADMINDESCRIPTION]:
item[AdministratorFields.ISSUPERADMINDESCRIPTION],
- [AdministratorFields.IS_DELETED]:
- item[AdministratorFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [AdministratorFields.DATA_INS_USER]:
+ item[AdministratorFields.DATA_INS_USER],
+ [AdministratorFields.DATA_INS_DATE]:
+ item[AdministratorFields.DATA_INS_DATE],
+ [AdministratorFields.DATA_CHG_USER]:
+ item[AdministratorFields.DATA_CHG_USER],
+ [AdministratorFields.DATA_CHG_DATE]:
+ item[AdministratorFields.DATA_CHG_DATE],
[AdministratorFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -535,8 +621,8 @@ const fetchAdministratorData = async () => {
const fetchSelectAdminTypes = async () => {
try {
const result = await fetchAdminTypes({
- [AdministratorTypeFields.IS_DELETED]: 0,
- [AdministratorTypeFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
});
adminTypeOptions.value = result.Items.map((item) => ({
label: item[AdministratorTypeFields.NAME],
@@ -551,10 +637,16 @@ onMounted(() => {
Promise.all([fetchAdministratorData(), fetchSelectAdminTypes()]);
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertAdministrator");
- form[AdministratorFields.NUMBER] = generateSnowflakeId({
+ form[AdministratorFields.NUMBER] = generateUniqueId({
prefix: "AD-",
separator: null,
});
@@ -566,6 +658,57 @@ const showModal = () => {
form[AdministratorFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: admins.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = admins.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchAdministratorData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: filteredColumns.value,
+ dataSource: admins.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ admins.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection(); // 重置选择
fetchAdministratorData();
@@ -643,7 +786,7 @@ const handleModalCancel = () => {
const restoreAdministrator = async (record) => {
try {
- record[AdministratorFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateAdministrator(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/systemmanagement/MenuManagementView.vue b/src/views/systemmanagement/MenuManagementView.vue
index 09b769e90189d4186c3e1625a0db3bc9ce5613b6..b421532a4e310ad7e9cc74b8fc78cc44bcf50304 100644
--- a/src/views/systemmanagement/MenuManagementView.vue
+++ b/src/views/systemmanagement/MenuManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
-
+
-
+
-
+
{{ selectedRecord?.[MenuFields.ICON] }}
+
+
+ {{ selectedRecord?.[MenuFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[MenuFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[MenuFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[MenuFields.DATA_CHG_DATE]) }}
+
+
@@ -172,25 +232,39 @@
import { ref, onMounted, computed, reactive } from "vue";
import { useRoute } from "vue-router";
import { getPageTitle } from "@/utils/pageTitle";
-import { showErrorNotification, showSuccessNotification } from "@/utils/index";
-import { fetchMenus, addMenu, updateMenu, deleteMenu } from "@/api/menuapi";
+import {
+ showErrorNotification,
+ showSuccessNotification,
+ exportTableToCsv,
+ formatDateTime,
+} from "@/utils/index";
+import {
+ fetchMenus,
+ addMenu,
+ updateMenu,
+ deleteMenu,
+} from "@/api/systemmanagement/menuapi";
import {
MenuFields,
initialFormValues,
getColumns,
getFormRules,
-} from "@/entities/menu.entity";
+} from "@/entities/systemmanagement/menu.entity";
import { useI18n } from "vue-i18n";
+import useModalForm from "@/composables/useModalForm";
import { emitter } from "@/utils/eventBus";
import ListFilter from "@/components/common/ListFilter.vue";
-import { getMenuFilterConfig } from "@/filters/menu.filter.config";
+import { getMenuFilterConfig } from "@/filters/systemmanagement/menu.filter.config";
import { MenuManagementPermissions } from "@/common/permissioncode";
+import { BaseFields } from "@/entities/common.entity";
const { t } = useI18n();
const route = useRoute();
const pageTitleKey = computed(() => getPageTitle(route.path));
const translatedPageTitle = computed(() => t(pageTitleKey.value));
const loading = ref(false);
+const exportLoadingMode = ref("");
+const exportProgress = ref(0);
const menus = ref([]);
const modalVisible = ref(false);
const viewModalVisible = ref(false);
@@ -210,12 +284,17 @@ const menuFilterConfig = computed(() => getMenuFilterConfig(t));
const form = reactive({ ...initialFormValues });
const rules = getFormRules(t);
+const { isFormValid } = useModalForm(form, rules);
const menuKeyLabel = computed(() => t("message.menuKey"));
const menuTitleLabel = computed(() => t("message.menuTitle"));
const menuPathLabel = computed(() => t("message.menuPath"));
const menuParentLabel = computed(() => t("message.menuParent"));
const menuIconLabel = computed(() => t("message.menuIcon"));
+const createdByLabel = computed(() => t("message.createdBy"));
+const createdTimeLabel = computed(() => t("message.createdTime"));
+const lastUpdatedByLabel = computed(() => t("message.lastUpdatedBy"));
+const lastUpdatedTimeLabel = computed(() => t("message.lastUpdatedTime"));
// 行选择配置
const rowSelection = computed(() => ({
@@ -225,7 +304,7 @@ const rowSelection = computed(() => ({
selectedRows.value = selectedRows;
},
getCheckboxProps: (record) => ({
- disabled: record[MenuFields.IS_DELETED] == 1,
+ disabled: record[BaseFields.IS_DELETED] == 1,
}),
}));
@@ -248,7 +327,7 @@ const selectRow = (record) => {
const customRow = (record) => {
return {
onClick: () => {
- if (record[MenuFields.IS_DELETED] === 1) {
+ if (record[BaseFields.IS_DELETED] === 1) {
return;
}
selectRow(record);
@@ -348,7 +427,7 @@ const buildServerParamsFromFilters = () => {
// 包含动态添加的字段,使用 effectiveConfig 而不是原始配置
const allFieldKeys = [
...menuFilterConfig.value?.fields.map((f) => f.name),
- MenuFields.IS_DELETED, // 确保包含 IsDelete 字段
+ BaseFields.IS_DELETED, // 确保包含 IsDelete 字段
];
allFieldKeys.forEach((fieldName) => {
@@ -363,7 +442,7 @@ const buildServerParamsFromFilters = () => {
}
// 对于动态字段,直接赋值
- if (fieldName === MenuFields.IS_DELETED) {
+ if (fieldName === BaseFields.IS_DELETED) {
params[fieldName] = val;
return;
}
@@ -435,13 +514,19 @@ const pagination = reactive({
showTotal: (total) => t("message.totalRecords", { total }),
});
-const fetchMenuData = async () => {
+const fetchMenuData = async (ignorePaging = false) => {
loading.value = true;
try {
const result = await fetchMenus({
- [MenuFields.PAGE]: pagination.current,
- [MenuFields.PAGE_SIZE]: pagination.pageSize,
- [MenuFields.IS_DELETED]: 0,
+ ...(ignorePaging
+ ? {
+ [BaseFields.IGNOREPAGING]: true,
+ }
+ : {
+ [BaseFields.PAGE]: pagination.current,
+ [BaseFields.PAGE_SIZE]: pagination.pageSize,
+ }),
+ [BaseFields.IS_DELETED]: 0,
...currentFilterParams,
});
if (result?.Items) {
@@ -453,7 +538,11 @@ const fetchMenuData = async () => {
[MenuFields.PATH]: item[MenuFields.PATH],
[MenuFields.PARENT]: item[MenuFields.PARENT],
[MenuFields.ICON]: item[MenuFields.ICON],
- [MenuFields.IS_DELETED]: item[MenuFields.IS_DELETED] ?? 0,
+ [BaseFields.IS_DELETED]: item[BaseFields.IS_DELETED] ?? 0,
+ [MenuFields.DATA_INS_USER]: item[MenuFields.DATA_INS_USER],
+ [MenuFields.DATA_INS_DATE]: item[MenuFields.DATA_INS_DATE],
+ [MenuFields.DATA_CHG_USER]: item[MenuFields.DATA_CHG_USER],
+ [MenuFields.DATA_CHG_DATE]: item[MenuFields.DATA_CHG_DATE],
[MenuFields.SELECTED]: false,
}));
pagination.total = result.TotalCount;
@@ -468,8 +557,8 @@ const fetchMenuData = async () => {
const fetchSelectMenus = async () => {
try {
const result = await fetchMenus({
- [MenuFields.IGNOREPAGING]: true,
- [MenuFields.IS_DELETED]: 0,
+ [BaseFields.IGNOREPAGING]: true,
+ [BaseFields.IS_DELETED]: 0,
[MenuFields.ID]: null,
});
menuOptions.value = result.Items.map((item) => ({
@@ -486,7 +575,13 @@ onMounted(() => {
fetchSelectMenus();
});
+const resetModalForm = () => {
+ formRef.value?.resetFields();
+ Object.assign(form, { ...initialFormValues });
+};
+
const showModal = () => {
+ resetModalForm();
modalVisible.value = true;
modalTitle.value = t("message.insertMenu");
form[MenuFields.KEY] = "";
@@ -497,6 +592,57 @@ const showModal = () => {
form[MenuFields.MODIFYSTATUS] = "insert";
};
+const handleExportMenuClick = async ({ key }) => {
+ if (exportLoadingMode.value !== "") {
+ return;
+ }
+
+ if (key === "all") {
+ await handleExportAllPages();
+ return;
+ }
+
+ await handleExportCurrentPage();
+};
+const handleExportCurrentPage = async () => {
+ exportLoadingMode.value = "current";
+ try {
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: menus.value,
+ });
+ } finally {
+ exportLoadingMode.value = "";
+ }
+};
+
+const handleExportAllPages = async () => {
+ const previousRows = menus.value;
+ const previousTotal = pagination.total;
+ const previousCurrent = pagination.current;
+ const previousPageSize = pagination.pageSize;
+ exportLoadingMode.value = "all";
+ exportProgress.value = 10;
+ try {
+ await fetchMenuData(true);
+ exportProgress.value = 75;
+ exportTableToCsv({
+ filename: translatedPageTitle.value,
+ columns: columns.value,
+ dataSource: menus.value,
+ });
+ exportProgress.value = 100;
+ } finally {
+ menus.value = previousRows;
+ pagination.total = previousTotal;
+ pagination.current = previousCurrent;
+ pagination.pageSize = previousPageSize;
+ loading.value = false;
+ exportLoadingMode.value = "";
+ exportProgress.value = 0;
+ }
+};
const refreshData = () => {
clearSelection();
fetchMenuData();
@@ -550,7 +696,7 @@ const handleModalCancel = () => {
const restoreMenu = async (record) => {
try {
- record[MenuFields.IS_DELETED] = 0;
+ record[BaseFields.IS_DELETED] = 0;
const response = await updateMenu(record);
if (response && response.Success) {
showSuccessNotification(t("message.restoreSuccess"));
diff --git a/src/views/systemmanagement/QuartzJobListView.vue b/src/views/systemmanagement/QuartzJobListView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ffdd963fd0498b9324cb07a582aaa8e4e120c167
--- /dev/null
+++ b/src/views/systemmanagement/QuartzJobListView.vue
@@ -0,0 +1,409 @@
+
+
+
{{ translatedPageTitle }}
+
+
+ {{ $t("message.refreshData") }}
+
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ renderCellText(record[QuartzFields.TRIGGER_STATE]) }}
+
+
+
+ {{ formatUtcTime(record[column.key]) }}
+
+
+ {{ renderCellText(text) }}
+
+
+
+
+
+
+
diff --git a/src/views/systemmanagement/RoleGrantPermissionView.vue b/src/views/systemmanagement/RoleGrantPermissionView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..70bd9d8e41ce068cca65f3ba18c23c0f66aee57e
--- /dev/null
+++ b/src/views/systemmanagement/RoleGrantPermissionView.vue
@@ -0,0 +1,735 @@
+
+
+
{{ translatedPageTitle }}
+
+
+
+ {{ $t("message.roleNumber") }}:
+ {{ roleNumber }}
+ ({{ roleName }})
+
+
+
+ {{ $t("message.backToHome") }}
+
+
+ {{ $t("message.refreshData") }}
+
+
+ {{ $t("message.save") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ opt.label
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/systemmanagement/RoleManagementView.vue b/src/views/systemmanagement/RoleManagementView.vue
index d97d3a91a0309473108dfffab500c3611b32a456..b8cd98d9d20481f09205b1e7255fac6c4f72ee8b 100644
--- a/src/views/systemmanagement/RoleManagementView.vue
+++ b/src/views/systemmanagement/RoleManagementView.vue
@@ -1,10 +1,42 @@
-
+
{{ translatedPageTitle }}
{{ $t("message.refreshData") }}
+
+
+ {{ $t("message.exportData") }}
+
+
+
+
+
+ {{ $t("message.exportCurrentPage") }}
+
+
+ {{ $t("message.exportAllPages") }}
+
+
+
+
+
-
+
@@ -134,7 +171,10 @@
{{ form[RoleFields.NUMBER] }}
-
+
{{ selectedRecord?.[RoleFields.DESCRIPTION] || "-" }}
+
+
+ {{ selectedRecord?.[RoleFields.DATA_INS_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[RoleFields.DATA_INS_DATE]) }}
+
+
+
+
+ {{ selectedRecord?.[RoleFields.DATA_CHG_USER] }}
+
+
+ {{ formatDateTime(selectedRecord?.[RoleFields.DATA_CHG_DATE]) }}
+
+
-
-
-
-
- {{ $t("message.roleNumber") }}:
- {{ currentRoleNumber }}
-
-
(allRoleChecked = e.target.checked)"
- >
- {{ $t("message.choseAll") }}
-
-
-
-
-
-
-
-
-
-
- {{
- opt.label
- }}
-
-
-
-
-
-
-
-
-
+
{{ $t("message.roleNumber") }}:
{{ currentRoleNumberUsers }}
-
-
账号类型:
+
+ 账号类型:
{{ currentUserGroupTypeLabel }}
-
+
{{ roleUserOptionsHint }}
@@ -281,45 +263,56 @@
diff --git a/src/views/systemmanagement/UnifiedAccountPermissionView.vue b/src/views/systemmanagement/UnifiedAccountPermissionView.vue
index 8ce86e5f86e8972461db0104278c45f74bc5f911..100bb6e380371658878a389c19e148bfa94ea286 100644
--- a/src/views/systemmanagement/UnifiedAccountPermissionView.vue
+++ b/src/views/systemmanagement/UnifiedAccountPermissionView.vue
@@ -12,6 +12,7 @@
-
+
{{ waitSelectText }}
-
+
{{ selectedLabelText }}:
{{ selectedUserNumber }}
@@ -124,13 +126,15 @@
toggleDirectPerm(opt.value, e.target.checked)"
+ @change="
+ (e) => toggleDirectPerm(opt.value, e.target.checked)
+ "
/>
{{ opt.label }}
({{ $t("message.permissionGranted") }})
@@ -153,10 +157,14 @@ import { useI18n } from "vue-i18n";
import { getPageTitle } from "@/utils/pageTitle";
import { showErrorNotification, showSuccessNotification } from "@/utils/index";
import { emitter } from "@/utils/eventBus";
+import {
+ getStoredSessionItem,
+ SESSION_STORAGE_KEYS,
+} from "@/utils/tokenStorage";
-import { selectPermissionList } from "@/api/permissionapi";
+import { selectPermissionList } from "@/api/systemmanagement/permissionapi";
import { AccountPermissionPermissions } from "@/common/permissioncode";
-import { fetchRoles } from "@/api/roleapi";
+import { fetchRoles } from "@/api/systemmanagement/roleapi";
import {
fetchAdmins,
@@ -165,28 +173,28 @@ import {
readUserRolePermissions as readAdminUserRolePermissions,
assignUserPermissions as assignAdminUserPermissions,
readUserDirectPermissions as readAdminUserDirectPermissions,
-} from "@/api/administratorapi";
-import { fetchEmployees } from "@/api/employeeapi";
+} from "@/api/systemmanagement/administratorapi";
+import { fetchEmployees } from "@/api/humanresourcemanagement/employeeapi";
import {
readUserRoles as readEmpUserRoles,
assignUserRoles as assignEmpUserRoles,
readUserRolePermissions as readEmpUserRolePermissions,
assignUserPermissions as assignEmpUserPermissions,
readUserDirectPermissions as readEmpUserDirectPermissions,
-} from "@/api/employeepermissionapi";
-import { fetchCustomers } from "@/api/customerapi";
+} from "@/api/systemmanagement/employeepermissionapi";
+import { fetchCustomers } from "@/api/customermanagement/customerapi";
import {
readUserRoles as readCusUserRoles,
assignUserRoles as assignCusUserRoles,
readUserRolePermissions as readCusUserRolePermissions,
assignUserPermissions as assignCusUserPermissions,
readUserDirectPermissions as readCusUserDirectPermissions,
-} from "@/api/customerpermissionapi";
+} from "@/api/systemmanagement/customerpermissionapi";
-import { AdministratorFields } from "@/entities/administrator.entity";
-import { EmployeeFields } from "@/entities/employee.entity";
-import { CustomerFields } from "@/entities/customer.entity";
-import { RoleFields } from "@/entities/role.entity";
+import { AdministratorFields } from "@/entities/systemmanagement/administrator.entity";
+import { EmployeeFields } from "@/entities/humanresourcemanagement/employee.entity";
+import { CustomerFields } from "@/entities/customermanagement/customer.entity";
+import { RoleFields } from "@/entities/systemmanagement/role.entity";
const props = defineProps({
sourceType: {
@@ -387,10 +395,12 @@ const rolePermColumns = [
dataIndex: "PermissionName",
key: "PermissionName",
},
+ { title: t("message.menuId"), dataIndex: "MenuId", key: "MenuId" },
{ title: t("message.menuKey"), dataIndex: "MenuKey", key: "MenuKey" },
+ { title: t("message.menuTitle"), dataIndex: "MenuName", key: "MenuName" },
];
const rpRowKey = (rec) =>
- `${rec.RoleNumber || ""}-${rec.PermissionNumber || ""}`;
+ `${rec.RoleNumber || ""}-${rec.PermissionNumber || ""}-${rec.MenuId || ""}`;
// 配置映射
const adminColumns = [
@@ -572,7 +582,26 @@ const listTitleText = computed(() => safeT(listTitleKey.value));
const waitSelectText = computed(() => safeT(waitSelectKey.value));
const selectedLabelText = computed(() => safeT(selectedLabelKey.value));
+const getCurrentLoginType = () => {
+ try {
+ return String(
+ getStoredSessionItem(SESSION_STORAGE_KEYS.LOGIN_TYPE) || "admin",
+ )
+ .trim()
+ .toLowerCase();
+ } catch {
+ return "admin";
+ }
+};
+
+const shouldRefreshCurrentSessionPermissions = () => {
+ const currentLoginType = getCurrentLoginType();
+ if (currentLoginType !== "admin") return true;
+ return inferredType.value === "admin";
+};
+
const refreshCurrentSessionPermissions = () => {
+ if (!shouldRefreshCurrentSessionPermissions()) return;
emitter.emit("refresh-menu");
};
@@ -727,7 +756,7 @@ const refreshUsers = () => loadUsers();
// 预选用户逻辑
const tryPreselectFromAccount = () => {
try {
- const acc = localStorage.getItem("account");
+ const acc = getStoredSessionItem(SESSION_STORAGE_KEYS.ACCOUNT);
if (!acc) return false;
const accStr = String(acc);
if (curConf.value.preselectMode === "accountAlways") {
@@ -785,6 +814,17 @@ onMounted(async () => {
min-width: 0;
}
+.permission-placeholder {
+ padding: 16px;
+ color: var(--app-text-secondary);
+}
+
+.permission-selected-user {
+ margin-bottom: 10px;
+ font-size: 13px;
+ color: var(--app-text-secondary);
+}
+
.permission-option-text {
min-width: 0;
white-space: normal;
@@ -805,6 +845,12 @@ onMounted(async () => {
font-weight: 600;
}
+.permission-granted-hint {
+ margin-left: 6px;
+ color: var(--app-text-secondary);
+ font-size: 12px;
+}
+
@media (max-width: 1400px) {
.permission-option-grid {
grid-template-columns: 1fr !important;