-
Couldn't load subscription status.
- Fork 8.2k
feat: increase support for multiple time zones #6839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
0a8339a
b029f77
61ce53b
e3e5755
e01803c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| }); |
| 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(); | ||
| }); |
| 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()); | ||
| }); |
| 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; | ||
| }; | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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'; |
| 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 }); | ||
| } |
| 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'; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unify ExtendedModalApi source with widget (popup-ui). -import type { ExtendedModalApi } from '@vben/common-ui';
+import type { ExtendedModalApi } from '@vben-core/popup-ui';📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| 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'; | ||||||
|
|
@@ -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[]>([ | ||||||
|
|
@@ -60,6 +65,29 @@ const showDot = computed(() => | |||||
| notifications.value.some((item) => !item.isRead), | ||||||
| ); | ||||||
|
|
||||||
| const userProfileStore = useUserProfileStore(); | ||||||
| const computedTimezone = computed(() => userProfileStore.timezone); | ||||||
|
|
||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| 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: () => { | ||||||
|
|
@@ -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" | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| export * from './auth'; | ||
| export * from './user-profile'; |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for timezone persistence. The 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||
| * 初始化用户时区 | ||||||||||||||||||||||||||||||||||||||||||
| * 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| initTimezone(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||
| timezone: timezoneRef, | ||||||||||||||||||||||||||||||||||||||||||
| setTimezone, | ||||||||||||||||||||||||||||||||||||||||||
| initTimezone, | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export { useUserProfileStore }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 解决热更新问题 | ||||||||||||||||||||||||||||||||||||||||||
| const hot = import.meta.hot; | ||||||||||||||||||||||||||||||||||||||||||
| if (hot) { | ||||||||||||||||||||||||||||||||||||||||||
| hot.accept(acceptHMRUpdate(useUserProfileStore, hot)); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| 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'; |
| 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 }); | ||
| } |
| 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'; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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
Suggested change
🤖 Prompt for AI Agents |
||||||
| 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'; | ||||||
|
|
@@ -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[]>([ | ||||||
|
|
@@ -60,6 +65,29 @@ const showDot = computed(() => | |||||
| notifications.value.some((item) => !item.isRead), | ||||||
| ); | ||||||
|
|
||||||
| const userProfileStore = useUserProfileStore(); | ||||||
| const computedTimezone = computed(() => userProfileStore.timezone); | ||||||
|
|
||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
| 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: () => { | ||||||
|
|
@@ -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" | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| export * from './auth'; | ||
| export * from './user-profile'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Also applies to: 400-421
🤖 Prompt for AI Agents