Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 9 additions & 0 deletions apps/web-antd/src/layouts/basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { NotificationItem } from '@vben/layouts';

import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';

import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
Expand Down Expand Up @@ -52,6 +53,7 @@ const notifications = ref<NotificationItem[]>([
},
]);

const router = useRouter();
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
Expand All @@ -61,6 +63,13 @@ const showDot = computed(() =>
);

const menus = computed(() => [
{
handler: () => {
router.push({ name: 'Profile' });
},
icon: 'lucide:user',
text: $t('page.auth.profile'),
},
{
handler: () => {
openWindow(VBEN_DOC_URL, {
Expand Down
3 changes: 2 additions & 1 deletion apps/web-antd/src/locales/langs/en-US/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
"forgetPassword": "Forget Password",
"profile": "Profile"
},
"dashboard": {
"title": "Dashboard",
Expand Down
3 changes: 2 additions & 1 deletion apps/web-antd/src/locales/langs/zh-CN/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"profile": "个人中心"
},
"dashboard": {
"title": "概览",
Expand Down
10 changes: 10 additions & 0 deletions apps/web-antd/src/router/routes/modules/vben.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ const routes: RouteRecordRaw[] = [
order: 9999,
},
},
{
name: 'Profile',
path: '/profile',
component: () => import('#/views/_core/profile/index.vue'),
meta: {
icon: 'lucide:user',
hideInMenu: true,
title: $t('page.auth.profile'),
},
},
];

export default routes;
65 changes: 65 additions & 0 deletions apps/web-antd/src/views/_core/profile/base-setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<script setup lang="ts">
import type { BasicOption } from '@vben/types';

import type { VbenFormSchema } from '#/adapter/form';

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

import { ProfileBaseSetting } from '@vben/common-ui';

import { getUserInfoApi } from '#/api';

const profileBaseSettingRef = ref();

const MOCK_ROLES_OPTIONS: BasicOption[] = [
{
label: '管理员',
value: 'super',
},
{
label: '用户',
value: 'user',
},
{
label: '测试',
value: 'test',
},
];

const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'realName',
component: 'Input',
label: '姓名',
},
{
fieldName: 'username',
component: 'Input',
label: '用户名',
},
{
fieldName: 'roles',
component: 'Select',
componentProps: {
mode: 'tags',
options: MOCK_ROLES_OPTIONS,
},
label: '角色',
},
{
fieldName: 'introduction',
component: 'Textarea',
label: '个人简介',
},
];
});
Comment on lines +14 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use i18n for labels instead of hardcoded Chinese text.

Both MOCK_ROLES_OPTIONS and formSchema contain hardcoded Chinese labels. For proper internationalization support, these should use translation keys via $t() or similar i18n utilities.

Example refactor:

 const MOCK_ROLES_OPTIONS: BasicOption[] = [
   {
-    label: '管理员',
+    label: $t('profile.roles.admin'),
     value: 'super',
   },
   // ... other options
 ];

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web-antd/src_views/_core/profile/base-setting.vue around lines 14 to 56,
the MOCK_ROLES_OPTIONS and formSchema use hardcoded Chinese labels; replace
these with i18n translation keys by calling the translator (e.g., const { t } =
useI18n() or this.$t) when building options and schema so labels become
t('profile.role.admin'), t('profile.label.name'), etc.; add/choose appropriate
translation keys in the locale files and ensure componentProps.options and
schema.label use the translated strings (keep keys consistent and computed so
reactivity works).


onMounted(async () => {
const data = await getUserInfoApi();
profileBaseSettingRef.value.getFormApi().setValues(data);
});
Comment on lines +58 to +61
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 and null check for the API call.

The onMounted hook has two issues:

  1. No error handling: If getUserInfoApi() fails, the error is unhandled and users receive no feedback
  2. Missing null check: profileBaseSettingRef.value could be null/undefined if the component hasn't rendered yet

Apply this diff:

 onMounted(async () => {
-  const data = await getUserInfoApi();
-  profileBaseSettingRef.value.getFormApi().setValues(data);
+  try {
+    const data = await getUserInfoApi();
+    if (profileBaseSettingRef.value) {
+      profileBaseSettingRef.value.getFormApi().setValues(data);
+    }
+  } catch (error) {
+    console.error('Failed to load user profile:', error);
+    // Consider showing user-facing error notification
+  }
 });
📝 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
onMounted(async () => {
const data = await getUserInfoApi();
profileBaseSettingRef.value.getFormApi().setValues(data);
});
onMounted(async () => {
try {
const data = await getUserInfoApi();
if (profileBaseSettingRef.value) {
profileBaseSettingRef.value.getFormApi().setValues(data);
}
} catch (error) {
console.error('Failed to load user profile:', error);
// Consider showing user-facing error notification
}
});
🤖 Prompt for AI Agents
In apps/web-antd/src/views/_core/profile/base-setting.vue around lines 58 to 61,
the onMounted block lacks error handling and does not guard against
profileBaseSettingRef.value being null; wrap the async call in a try/catch,
await getUserInfoApi() inside the try, check that profileBaseSettingRef.value
and profileBaseSettingRef.value.getFormApi() are defined (or await nextTick()
before accessing) and only call setValues when data is non-null, and in the
catch log the error and surface user feedback (e.g., show an error notification)
so failures are handled gracefully.

</script>
<template>
<ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" />
</template>
49 changes: 49 additions & 0 deletions apps/web-antd/src/views/_core/profile/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script setup lang="ts">
import { ref } from 'vue';

import { Profile } from '@vben/common-ui';
import { useUserStore } from '@vben/stores';

import ProfileBase from './base-setting.vue';
import ProfileNotificationSetting from './notification-setting.vue';
import ProfilePasswordSetting from './password-setting.vue';
import ProfileSecuritySetting from './security-setting.vue';

const userStore = useUserStore();

const tabsValue = ref<string>('basic');

const tabs = ref([
{
label: '基本设置',
value: 'basic',
},
{
label: '安全设置',
value: 'security',
},
{
label: '修改密码',
value: 'password',
},
{
label: '新消息提醒',
value: 'notice',
},
]);
Comment on lines +16 to +33
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Internationalize hardcoded Chinese labels.

Tab labels are hardcoded in Chinese. Use the i18n system for multi-language support.

🤖 Prompt for AI Agents
In apps/web-antd/src/views/_core/profile/index.vue around lines 16 to 33, the
tab labels are hardcoded in Chinese; replace them with i18n lookups by
importing/using the locale composable (useI18n or $t) in the component setup and
build the tabs array with t('profile.tabs.basic'), t('profile.tabs.security'),
t('profile.tabs.password'), t('profile.tabs.notice') (or equivalent keys); also
add those keys to your locale JSON/YAML files for all supported languages so
translations exist and keep the tab value fields unchanged.

</script>
<template>
<Profile
v-model:model-value="tabsValue"
title="个人中心"
:user-info="userStore.userInfo"
:tabs="tabs"
>
<template #content>
<ProfileBase v-if="tabsValue === 'basic'" />
<ProfileSecuritySetting v-if="tabsValue === 'security'" />
<ProfilePasswordSetting v-if="tabsValue === 'password'" />
<ProfileNotificationSetting v-if="tabsValue === 'notice'" />
</template>
</Profile>
</template>
31 changes: 31 additions & 0 deletions apps/web-antd/src/views/_core/profile/notification-setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from 'vue';

import { ProfileNotificationSetting } from '@vben/common-ui';

const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '其他用户的消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'systemMessage',
label: '系统消息',
description: '系统消息将以站内信的形式通知',
},
{
value: true,
fieldName: 'todoTask',
label: '待办任务',
description: '待办任务将以站内信的形式通知',
},
Comment on lines +8 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.

🛠️ Refactor suggestion | 🟠 Major

Extract hardcoded Chinese text to i18n resource files.

All labels and descriptions are hardcoded in Chinese, preventing internationalization.

Extract to locale files and use i18n keys like $t('profile.notification.accountPassword.label') and $t('profile.notification.accountPassword.description').

🤖 Prompt for AI Agents
In apps/web-antd/src/views/_core/profile/notification-setting.vue around lines 8
to 25, the notification item labels and descriptions are hardcoded in Chinese;
replace each literal with i18n lookups (e.g.
$t('profile.notification.accountPassword.label') and
$t('profile.notification.accountPassword.description')) and add corresponding
keys for accountPassword, systemMessage and todoTask in the locale resource
files (both zh and en or other supported locales). Keep existing fieldName and
value fields unchanged, ensure the i18n keys follow the suggested hierarchy
(profile.notification.<field>.(label|description)), and provide sensible
fallback strings in the locale files.

];
});
</script>
<template>
<ProfileNotificationSetting :form-schema="formSchema" />
</template>
66 changes: 66 additions & 0 deletions apps/web-antd/src/views/_core/profile/password-setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<script setup lang="ts">
import type { VbenFormSchema } from '#/adapter/form';

import { computed, ref } from 'vue';

import { ProfilePasswordSetting, z } from '@vben/common-ui';

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

const profilePasswordSettingRef = ref();

const formSchema = computed((): VbenFormSchema[] => {
return [
{
fieldName: 'oldPassword',
label: '旧密码',
component: 'VbenInputPassword',
componentProps: {
placeholder: '请输入旧密码',
},
},
{
fieldName: 'newPassword',
label: '新密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请输入新密码',
},
},
{
fieldName: 'confirmPassword',
label: '确认密码',
component: 'VbenInputPassword',
componentProps: {
passwordStrength: true,
placeholder: '请再次输入新密码',
},
dependencies: {
rules(values) {
const { newPassword } = values;
return z
.string({ required_error: '请再次输入新密码' })
.min(1, { message: '请再次输入新密码' })
.refine((value) => value === newPassword, {
message: '两次输入的密码不一致',
});
},
triggerFields: ['newPassword'],
},
},
];
});

function handleSubmit() {
message.success('密码修改成功');
}
Comment on lines +55 to +57
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

🧩 Analysis chain

Missing API implementation for password change.

The submit handler only displays a client-side success message without making an actual API call to change the password on the server.

Implement the password change API call before the success message:

-function handleSubmit() {
+async function handleSubmit(values: Record<string, any>) {
+  try {
+    // Example: await changePasswordApi(values);
+    // TODO: Implement actual API call
-  message.success('密码修改成功');
+    message.success('密码修改成功');
+  } catch (error) {
+    message.error('密码修改失败');
+  }
}

Run the following script to verify if a password change API already exists in the codebase:


🏁 Script executed:

#!/bin/bash
# Search for password change API implementations
rg -nP --type=ts 'password|Password' -g '**/api/**' -C2

Length of output: 2854


🏁 Script executed:

#!/bin/bash
# Search for password change/update API with various naming patterns
rg -nP '(change|update|modify|set).*[Pp]assword' --type=ts -C2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look at the password-setting.vue component to understand context
cat apps/web-antd/src/views/_core/profile/password-setting.vue

Length of output: 1516


🏁 Script executed:

#!/bin/bash
# Search for ProfilePasswordSetting component definition
fd -type f -name "*.ts" -o -name "*.vue" | xargs grep -l "ProfilePasswordSetting" | head -20

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Search for any password endpoint in backend (beyond login)
find . -type f \( -name "*.ts" -o -name "*.js" \) -path "*/api/*" -o -path "*/backend*" | xargs grep -l "password" 2>/dev/null | grep -v node_modules

Length of output: 305


🏁 Script executed:

#!/bin/bash
# Check if handleSubmit receives parameters from the form component
rg -nP "handleSubmit|@submit" apps/web-antd/src/views/_core/profile/password-setting.vue -A5 -B5

Length of output: 308


🏁 Script executed:

#!/bin/bash
# Fix the fd command - search for ProfilePasswordSetting
rg -l "ProfilePasswordSetting" --type ts --type vue | head -20

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Check backend-mock structure to understand API patterns
find apps/backend-mock/api -type f -name "*.ts" | head -20

Length of output: 830


🏁 Script executed:

#!/bin/bash
# Search for other submit handlers to understand parameter passing pattern
rg -nP "function\s+handle\w+\s*\([^)]*\)\s*\{" apps/web-antd/src/views/ -C2 | head -50

Length of output: 1754


Missing API implementation AND handler parameter for password change.

The submit handler doesn't accept form values and has no API call to change the password on the server. Other similar forms in the codebase (register, forget-password) correctly receive form values and handle them.

Two issues to fix:

  1. Handler signature: Add parameter to receive form values (currently receives nothing)
  2. Backend endpoint: No password change endpoint exists in the backend
  3. API call: Add the actual API call before the success message
-function handleSubmit() {
+async function handleSubmit(values: Record<string, any>) {
+  try {
+    // Example: await changePasswordApi(values);
+    // TODO: Implement actual API call and create backend endpoint
-  message.success('密码修改成功');
+    message.success('密码修改成功');
+  } catch (error) {
+    message.error('密码修改失败');
+  }
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web-antd/src/views/_core/profile/password-setting.vue around lines 55 to
57, the handleSubmit function currently has no parameter and does not call the
backend to change the password; update the handler to accept the form values
parameter (e.g., values or formData), call the appropriate password-change API
endpoint (implement the backend endpoint if missing) using those values and
await its response, handle errors (show error message on failure) and only show
message.success('密码修改成功') after the API confirms success.

</script>
<template>
<ProfilePasswordSetting
ref="profilePasswordSettingRef"
class="w-1/3"
:form-schema="formSchema"
@submit="handleSubmit"
/>
</template>
43 changes: 43 additions & 0 deletions apps/web-antd/src/views/_core/profile/security-setting.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue';

import { ProfileSecuritySetting } from '@vben/common-ui';

const formSchema = computed(() => {
return [
{
value: true,
fieldName: 'accountPassword',
label: '账户密码',
description: '当前密码强度:强',
},
{
value: true,
fieldName: 'securityPhone',
label: '密保手机',
description: '已绑定手机:138****8293',
},
{
value: true,
fieldName: 'securityQuestion',
label: '密保问题',
description: '未设置密保问题,密保问题可有效保护账户安全',
},
{
value: true,
fieldName: 'securityEmail',
label: '备用邮箱',
description: '已绑定邮箱:ant***sign.com',
},
{
value: false,
fieldName: 'securityMfa',
label: 'MFA 设备',
description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
},
];
});
</script>
<template>
<ProfileSecuritySetting :form-schema="formSchema" />
</template>
9 changes: 9 additions & 0 deletions apps/web-ele/src/layouts/basic.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { NotificationItem } from '@vben/layouts';

import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';

import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
Expand Down Expand Up @@ -52,6 +53,7 @@ const notifications = ref<NotificationItem[]>([
},
]);

const router = useRouter();
const userStore = useUserStore();
const authStore = useAuthStore();
const accessStore = useAccessStore();
Expand All @@ -61,6 +63,13 @@ const showDot = computed(() =>
);

const menus = computed(() => [
{
handler: () => {
router.push({ name: 'Profile' });
},
icon: 'lucide:user',
text: $t('page.auth.profile'),
},
{
handler: () => {
openWindow(VBEN_DOC_URL, {
Expand Down
3 changes: 2 additions & 1 deletion apps/web-ele/src/locales/langs/en-US/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
"forgetPassword": "Forget Password",
"profile": "Profile"
},
"dashboard": {
"title": "Dashboard",
Expand Down
3 changes: 2 additions & 1 deletion apps/web-ele/src/locales/langs/zh-CN/page.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"profile": "个人中心"
},
"dashboard": {
"title": "概览",
Expand Down
10 changes: 10 additions & 0 deletions apps/web-ele/src/router/routes/modules/vben.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ const routes: RouteRecordRaw[] = [
order: 9999,
},
},
{
name: 'Profile',
path: '/profile',
component: () => import('#/views/_core/profile/index.vue'),
meta: {
icon: 'lucide:user',
hideInMenu: true,
title: $t('page.auth.profile'),
},
},
];

export default routes;
Loading
Loading