Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/backend-mock/api/profile/timezone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { eventHandler } from 'h3';
import { TIME_ZONE_OPTIONS } from '~/utils/mock-data';
import { useResponseSuccess } from '~/utils/response';

export default eventHandler(() => {
return useResponseSuccess(TIME_ZONE_OPTIONS);
});
14 changes: 14 additions & 0 deletions apps/backend-mock/api/user/setTimezone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { eventHandler, readBody } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
import { setTimezone } from '~/utils/timezone-utils';

export default eventHandler(async (event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
const { timezone } = await readBody(event);
setTimezone(timezone);
return useResponseSuccess();
});
12 changes: 12 additions & 0 deletions apps/backend-mock/api/user/timezone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { eventHandler } from 'h3';
import { verifyAccessToken } from '~/utils/jwt-utils';
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
import { getTimezone } from '~/utils/timezone-utils';

export default eventHandler((event) => {
const userinfo = verifyAccessToken(event);
if (!userinfo) {
return unAuthorizedResponse(event);
}
return useResponseSuccess(getTimezone());
});
31 changes: 31 additions & 0 deletions apps/backend-mock/utils/mock-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export interface UserInfo {
homePath?: string;
}

export interface TimeZoneOption {
offset: number;
timeZone: string;
}
Comment on lines +10 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unify timezone key and type name; current code won’t type‑check.

Interface uses timeZone while data uses timezone. Also type is TimeZoneOption vs shared TimezoneOption elsewhere.

-export interface TimeZoneOption {
-  offset: number;
-  timeZone: string;
-}
+export interface TimezoneOption {
+  offset: number;
+  timezone: string;
+}
@@
-export const TIME_ZONE_OPTIONS: TimeZoneOption[] = [
+export const TIME_ZONE_OPTIONS: TimezoneOption[] = [

Also applies to: 400-421

🤖 Prompt for AI Agents
In apps/backend-mock/utils/mock-data.ts around lines 10 to 13 (and similarly
lines ~400-421), the interface and the data are mismatched: the interface
declares timeZone: string and is named TimeZoneOption while the actual mock
objects use timezone and the shared type elsewhere is named TimezoneOption;
update the file so the property name and type name match the rest of the
codebase — either rename the interface to export interface TimezoneOption {
offset: number; timezone: string; } or change the mock data to use timeZone and
export the interface as TimeZoneOption consistently, and update any
imports/usages to reference the unified type name.


export const MOCK_USERS: UserInfo[] = [
{
id: 0,
Expand Down Expand Up @@ -388,3 +393,29 @@ export function getMenuIds(menus: any[]) {
});
return ids;
}

/**
* 时区选项
*/
export const TIME_ZONE_OPTIONS: TimeZoneOption[] = [
{
offset: -5,
timezone: 'America/New_York',
},
{
offset: 0,
timezone: 'Europe/London',
},
{
offset: 8,
timezone: 'Asia/Shanghai',
},
{
offset: 9,
timezone: 'Asia/Tokyo',
},
{
offset: 9,
timezone: 'Asia/Seoul',
},
];
10 changes: 10 additions & 0 deletions apps/backend-mock/utils/timezone-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let mockTimeZone: null | string = null;

export const setTimezone = (timeZone: string) => {
mockTimeZone = timeZone;
};

export const getTimezone = () => {
console.log('mockTimeZone', mockTimeZone);
return mockTimeZone;
};
1 change: 1 addition & 0 deletions apps/web-antd/src/api/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './auth';
export * from './menu';
export * from './user';
export * from './user-profile';
23 changes: 23 additions & 0 deletions apps/web-antd/src/api/core/user-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { TimezoneOption } from '@vben/types';

import { requestClient } from '#/api/request';

/**
* 获取系统支持的时区列表
*/
export async function getTimezoneOptionsApi() {
return requestClient.get<TimezoneOption[]>('/profile/timezone');
}
/**
* 获取用户时区
*/
export async function getUserTimezoneApi(): Promise<null | string | undefined> {
return requestClient.get<null | string | undefined>('/user/timezone');
}
/**
* 设置用户时区
* @param timezone 时区
*/
export async function setUserTimezoneApi(timezone: string) {
return requestClient.post('/user/setTimezone', { timezone });
}
40 changes: 38 additions & 2 deletions apps/web-antd/src/layouts/basic.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts" setup>
import type { ExtendedModalApi } from '@vben/common-ui';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unify ExtendedModalApi source with widget (popup-ui).

-import type { ExtendedModalApi } from '@vben/common-ui';
+import type { ExtendedModalApi } from '@vben-core/popup-ui';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { ExtendedModalApi } from '@vben/common-ui';
import type { ExtendedModalApi } from '@vben-core/popup-ui';
🤖 Prompt for AI Agents
In apps/web-antd/src/layouts/basic.vue around line 2, the ExtendedModalApi type
is imported from '@vben/common-ui' but should be unified with the widget's
popup-ui; change the import source to the widget module (e.g.
'@vben/widget/popup-ui'), update the import statement accordingly, and ensure
TypeScript path/module resolution is correct so the new module compiles.

import type { NotificationItem } from '@vben/layouts';

import { computed, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';

import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
Expand All @@ -11,14 +12,18 @@ import {
BasicLayout,
LockScreen,
Notification,
TimezoneButton,
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';

import { message } from 'ant-design-vue';

import { getTimezoneOptionsApi } from '#/api';
import { $t } from '#/locales';
import { useAuthStore } from '#/store';
import { useAuthStore, useUserProfileStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';

const notifications = ref<NotificationItem[]>([
Expand Down Expand Up @@ -60,6 +65,29 @@ const showDot = computed(() =>
notifications.value.some((item) => !item.isRead),
);

const userProfileStore = useUserProfileStore();
const computedTimezone = computed(() => userProfileStore.timezone);

const timezoneOptions = ref<string[]>([]);
onMounted(async () => {
timezoneOptions.value = ((await getTimezoneOptionsApi()) || []).map(
(item) => item.timezone,
);
});
const handleSetTimezone = async (
timezone: string,
modalApi: ExtendedModalApi,
) => {
try {
modalApi.setState({ confirmLoading: true });
await userProfileStore.setTimezone(timezone);
message.success($t('ui.widgets.timezone.setSuccess'));
modalApi.close();
} finally {
modalApi.setState({ confirmLoading: false });
}
};

const menus = computed(() => [
{
handler: () => {
Expand Down Expand Up @@ -147,6 +175,14 @@ watch(
@make-all="handleMakeAll"
/>
</template>
<template #timezone>
<TimezoneButton
:ok-handler="handleSetTimezone"
:timezone="computedTimezone"
:timezone-options="timezoneOptions"
name="out"
/>
</template>
<template #extra>
<AuthenticationLoginExpiredModal
v-model:open="accessStore.loginExpired"
Expand Down
1 change: 1 addition & 0 deletions apps/web-antd/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './auth';
export * from './user-profile';
56 changes: 56 additions & 0 deletions apps/web-antd/src/store/user-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ref } from 'vue';

import { getTimezone, setDefaultTimezone } from '@vben/utils';

import { acceptHMRUpdate, defineStore } from 'pinia';

import { getUserTimezoneApi, setUserTimezoneApi } from '#/api';

const useUserProfileStore = defineStore('user-profile', () => {
const timezoneRef = ref(
getTimezone() || new Intl.DateTimeFormat().resolvedOptions().timeZone,
);

/**
* 设置用户时区
* Set the user's timezone
* @param timezone 时区字符串
*/
async function setTimezone(timezone: string) {
timezoneRef.value = timezone;
// 设置dayjs默认时区
setDefaultTimezone(timezone);
// 保存用户的时区设置
await setUserTimezoneApi(timezone);
}
Comment on lines 19 to 25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for timezone persistence.

The setTimezone function updates local state before confirming the server update succeeds. If setUserTimezoneApi fails, the local timezone will be out of sync with the server.

Consider this approach:

  async function setTimezone(timezone: string) {
-   timezoneRef.value = timezone;
-   // 设置dayjs默认时区
-   setDefaultTimezone(timezone);
-   // 保存用户的时区设置
-   await setUserTimezoneApi(timezone);
+   try {
+     // 保存用户的时区设置
+     await setUserTimezoneApi(timezone);
+     // 只有在服务器更新成功后才更新本地状态
+     timezoneRef.value = timezone;
+     // 设置dayjs默认时区
+     setDefaultTimezone(timezone);
+   } catch (error) {
+     console.error('Failed to set timezone:', error);
+     throw error; // Re-throw to allow caller to handle
+   }
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function setTimezone(timezone: string) {
timezoneRef.value = timezone;
// 设置dayjs默认时区
setDefaultTimezone(timezone);
// 保存用户的时区设置
await setUserTimezoneApi(timezone);
}
async function setTimezone(timezone: string) {
try {
// 保存用户的时区设置
await setUserTimezoneApi(timezone);
// 只有在服务器更新成功后才更新本地状态
timezoneRef.value = timezone;
// 设置dayjs默认时区
setDefaultTimezone(timezone);
} catch (error) {
console.error('Failed to set timezone:', error);
throw error; // Re-throw to allow caller to handle
}
}
🤖 Prompt for AI Agents
In apps/web-antd/src/store/user-profile.ts around lines 19–25, setTimezone
currently updates timezoneRef and calls setDefaultTimezone before awaiting
setUserTimezoneApi, which can leave local state inconsistent if the API fails;
change the flow to await the API call inside a try/catch and only update
timezoneRef and call setDefaultTimezone after a successful response, and in the
catch log the error (or show user feedback) and do not change local state (or if
you prefer optimistic update, perform the update first but revert it in the
catch and notify the user); ensure the function rethrows or returns a failure
indicator so callers can react.


/**
* 初始化用户时区
* Initialize the user's timezone
*/
async function initTimezone() {
// 从服务器获取用户时区
const timezone = await getUserTimezoneApi();
if (timezone) {
timezoneRef.value = timezone;
// 设置dayjs默认时区
setDefaultTimezone(timezone);
}
}
Comment on lines +31 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for timezone initialization.

Missing error handling could lead to unhandled promise rejections if the API call fails.

  async function initTimezone() {
-   // 从服务器获取用户时区
-   const timezone = await getUserTimezoneApi();
-   if (timezone) {
-     timezoneRef.value = timezone;
-     // 设置dayjs默认时区
-     setDefaultTimezone(timezone);
-   }
+   try {
+     // 从服务器获取用户时区
+     const timezone = await getUserTimezoneApi();
+     if (timezone) {
+       timezoneRef.value = timezone;
+       // 设置dayjs默认时区
+       setDefaultTimezone(timezone);
+     }
+   } catch (error) {
+     console.error('Failed to initialize timezone:', error);
+     // Fallback to browser timezone on error
+   }
  }
🤖 Prompt for AI Agents
In apps/web-antd/src/store/user-profile.ts around lines 31 to 39, the
initTimezone function calls getUserTimezoneApi() without error handling which
can cause unhandled promise rejections; wrap the API call and subsequent logic
in a try/catch, on error log the error (or use a logger), optionally fall back
to a safe default such as Intl.DateTimeFormat().resolvedOptions().timeZone or
leave timezoneRef unchanged, and ensure setDefaultTimezone is only called when a
valid timezone string is obtained so the function always resolves gracefully
instead of propagating the rejection.


initTimezone();

return {
timezone: timezoneRef,
setTimezone,
initTimezone,
};
});

export { useUserProfileStore };

// 解决热更新问题
const hot = import.meta.hot;
if (hot) {
hot.accept(acceptHMRUpdate(useUserProfileStore, hot));
}
1 change: 1 addition & 0 deletions apps/web-ele/src/api/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './auth';
export * from './menu';
export * from './user';
export * from './user-profile';
23 changes: 23 additions & 0 deletions apps/web-ele/src/api/core/user-profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { TimezoneOption } from '@vben/types';

import { requestClient } from '#/api/request';

/**
* 获取系统支持的时区列表
*/
export async function getTimezoneOptionsApi() {
return requestClient.get<TimezoneOption[]>('/profile/timezone');
}
/**
* 获取用户时区
*/
export async function getUserTimezoneApi(): Promise<null | string | undefined> {
return requestClient.get<null | string | undefined>('/user/timezone');
}
/**
* 设置用户时区
* @param timezone 时区
*/
export async function setUserTimezoneApi(timezone: string) {
return requestClient.post('/user/setTimezone', { timezone });
}
40 changes: 38 additions & 2 deletions apps/web-ele/src/layouts/basic.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script lang="ts" setup>
import type { ExtendedModalApi } from '@vben/common-ui';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Unify modal API type with widget source.

TimezoneButton uses popup-ui’s modal; import the same ExtendedModalApi here to avoid TS incompatibility.

-import type { ExtendedModalApi } from '@vben/common-ui';
+import type { ExtendedModalApi } from '@vben-core/popup-ui';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import type { ExtendedModalApi } from '@vben/common-ui';
import type { ExtendedModalApi } from '@vben-core/popup-ui';
🤖 Prompt for AI Agents
In apps/web-ele/src/layouts/basic.vue around line 2, the file imports
ExtendedModalApi from '@vben/common-ui' which is a different modal API than the
one used by TimezoneButton; replace this import with the same ExtendedModalApi
export used by the popup-ui widget source (the exact module used by
TimezoneButton, e.g. import type { ExtendedModalApi } from '@popup-ui/common-ui'
or the popup-ui package path in the repo) so both components reference the
identical type and avoid TypeScript incompatibility.

import type { NotificationItem } from '@vben/layouts';

import { computed, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';

import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
Expand All @@ -11,14 +12,18 @@ import {
BasicLayout,
LockScreen,
Notification,
TimezoneButton,
UserDropdown,
} from '@vben/layouts';
import { preferences } from '@vben/preferences';
import { useAccessStore, useUserStore } from '@vben/stores';
import { openWindow } from '@vben/utils';

import { ElMessage } from 'element-plus';

import { getTimezoneOptionsApi } from '#/api';
import { $t } from '#/locales';
import { useAuthStore } from '#/store';
import { useAuthStore, useUserProfileStore } from '#/store';
import LoginForm from '#/views/_core/authentication/login.vue';

const notifications = ref<NotificationItem[]>([
Expand Down Expand Up @@ -60,6 +65,29 @@ const showDot = computed(() =>
notifications.value.some((item) => !item.isRead),
);

const userProfileStore = useUserProfileStore();
const computedTimezone = computed(() => userProfileStore.timezone);

const timezoneOptions = ref<string[]>([]);
onMounted(async () => {
timezoneOptions.value = ((await getTimezoneOptionsApi()) || []).map(
(item) => item.timezone,
);
});
const handleSetTimezone = async (
timezone: string,
modalApi: ExtendedModalApi,
) => {
try {
modalApi.setState({ confirmLoading: true });
await userProfileStore.setTimezone(timezone);
ElMessage.success($t('ui.widgets.timezone.setSuccess'));
modalApi.close();
} finally {
modalApi.setState({ confirmLoading: false });
}
};

const menus = computed(() => [
{
handler: () => {
Expand Down Expand Up @@ -147,6 +175,14 @@ watch(
@make-all="handleMakeAll"
/>
</template>
<template #timezone>
<TimezoneButton
:ok-handler="handleSetTimezone"
:timezone="computedTimezone"
:timezone-options="timezoneOptions"
name="out"
/>
</template>
<template #extra>
<AuthenticationLoginExpiredModal
v-model:open="accessStore.loginExpired"
Expand Down
1 change: 1 addition & 0 deletions apps/web-ele/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './auth';
export * from './user-profile';
Loading
Loading