From 053bb448786718c981a47fab6b3409731dbdc97a Mon Sep 17 00:00:00 2001 From: caixw Date: Thu, 10 Oct 2024 16:01:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin/pages/current):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20profile=20=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/messages/cmn-Hans.ts | 22 +++- admin/src/messages/en.ts | 19 +++- .../pages/current/{home.tsx => dashboard.tsx} | 4 +- admin/src/pages/current/index.ts | 28 +++-- admin/src/pages/current/profile.tsx | 107 ++++++++++++++++++ admin/src/pages/current/settings.tsx | 24 ++-- admin/src/pages/current/style.css | 14 +++ admin/src/pages/roles/index.ts | 2 +- admin/src/pages/system/index.ts | 14 +-- cmd/admin/src/main.tsx | 10 +- 10 files changed, 203 insertions(+), 41 deletions(-) rename admin/src/pages/current/{home.tsx => dashboard.tsx} (79%) create mode 100644 admin/src/pages/current/profile.tsx diff --git a/admin/src/messages/cmn-Hans.ts b/admin/src/messages/cmn-Hans.ts index 8cadc112..5443a393 100644 --- a/admin/src/messages/cmn-Hans.ts +++ b/admin/src/messages/cmn-Hans.ts @@ -16,6 +16,8 @@ const messages: Messages = { refresh: '刷新', areYouSure: '你确定要这么做吗?', page: { // 页面的翻译内容 + save: '保存', + update: '更新', editItem: '编辑', deleteItem: '删除', newItem: '新建', @@ -36,17 +38,26 @@ const messages: Messages = { unknown: '未知' }, current: { // 登录框 + dashboard: '仪表盘', + logout: '退出', + settings: '设置', + title: '登录', username: '账号', password: '密码', - home: '首页', - logout: '退出', - settings: '设置', + securitylog: '安全日志', content: '内容', ip: 'IP', ua: 'UA', uaInfo: '浏览器: {browser}({browserVersion}) 系统: {os}({osVersion}) 内核: {kernal}({kernalVersion})', + + profile: '个人信息', + name: '姓名', + nickname: '昵称', + oldPassword: '旧密码', + newPassword: '新密码', + confirmPassword: '确认新密码', }, system: { apis: 'API', @@ -157,6 +168,11 @@ const messages: Messages = { pageNotFound: '页面不存在', forbidden: '无权访问当前页面', internalServerError: '服务端错误', + + // 以下为一些内置的错误提示信息 + canNotBeEmpty: '不能为空', + oldNewPasswordCanNotBeEqual: '新密码不能与旧密码相同', + newConfirmPasswordMustBeEqual: '确认密码与新密码并不相同', }, theme: { mode: '主题模式', diff --git a/admin/src/messages/en.ts b/admin/src/messages/en.ts index cc529b4e..a847e17b 100644 --- a/admin/src/messages/en.ts +++ b/admin/src/messages/en.ts @@ -14,6 +14,8 @@ const messages = { refresh: 'refresh', areYouSure: 'are you sure?', page: { // 页面的翻译内容 + save: 'save', + update: 'update', editItem: 'edit item', deleteItem: 'delete item', newItem: 'new item', @@ -34,17 +36,26 @@ const messages = { unknown: 'unknown' }, current: { - home: 'Home', + dashboard: 'Dashboard', logout: 'logout', settings: 'Settings', + title: 'login', username: 'username', password: 'password', + securitylog: 'security logs', content: 'content', ip: 'IP', ua: 'user agent', uaInfo: 'browser: {browser}({browserVersion}) os: {os}({osVersion}) kernal: {kernal}({kernalVersion})', + + profile: 'profle', + name: 'name', + nickname: 'nickname', + oldPassword: 'old password', + newPassword: 'new password', + confirmPassword: 'confirm password', }, system: { apis: 'API', @@ -155,6 +166,12 @@ const messages = { pageNotFound: 'page not found', forbidden: 'forbidden', internalServerError: 'server error', + + // 以下为一些内置的错误提示信息 + canNotBeEmpty: 'can not be empty', + oldNewPasswordCanNotBeEqual: 'The old passowrd and new password can not be equal', + newConfirmPasswordMustBeEqual: 'The new passowrd and confirm password must be equal', + }, theme: { mode: 'theme mode', diff --git a/admin/src/pages/current/home.tsx b/admin/src/pages/current/dashboard.tsx similarity index 79% rename from admin/src/pages/current/home.tsx rename to admin/src/pages/current/dashboard.tsx index 626e48d3..aee420a2 100644 --- a/admin/src/pages/current/home.tsx +++ b/admin/src/pages/current/dashboard.tsx @@ -7,7 +7,7 @@ import { JSX } from 'solid-js'; import { Page } from '@/components'; export default function(): JSX.Element { - return + return home ; -} \ No newline at end of file +} diff --git a/admin/src/pages/current/index.ts b/admin/src/pages/current/index.ts index 760e220e..f21c24b4 100644 --- a/admin/src/pages/current/index.ts +++ b/admin/src/pages/current/index.ts @@ -2,22 +2,23 @@ // // SPDX-License-Identifier: MIT -import { Pages } from '@/pages/pages'; import { MenuItem, Route } from '@/app'; -import { default as Home } from './home'; -import { default as Settings } from './settings'; -import { default as Logout } from './logout'; +import { Pages } from '@/pages/pages'; +import { default as Dashboard } from './dashboard'; import { default as Login } from './login'; +import { default as Logout } from './logout'; +import { default as Profile } from './profile'; import { default as SecurityLogs } from './securitylogs'; +import { default as Settings } from './settings'; /** * 提供了与当前登录用户直接相关的页面 */ export class current implements Pages { /** - * 提供当前用户的首页面板 + * 提供当前用户的仪表盘 */ - static Home = Home; + static Dashboard = Dashboard; /** * 提供当前用户的设置面 @@ -34,6 +35,11 @@ export class current implements Pages { */ static Logout = Logout; + /** + * 当前用户的个人信息面板 + */ + static Profile = Profile; + /** * 用户的安全日志 */ @@ -51,7 +57,8 @@ export class current implements Pages { routes(): Array { return [ - { path: this.#prefix + '/home', component:Home }, + { path: this.#prefix + '/dashboard', component:Dashboard }, + { path: this.#prefix + '/profile', component:Profile }, { path: this.#prefix + '/settings', component: Settings }, { path: this.#prefix + '/securitylogs', component: SecurityLogs }, { path: this.#prefix + '/logout', component: Logout }, @@ -60,11 +67,12 @@ export class current implements Pages { menus(): Array { return [ - { type: 'item', label: '_i.page.current.home', path: this.#prefix + '/home', icon: 'home' }, + { type: 'item', label: '_i.page.current.dashboard', path: this.#prefix + '/dashboard', icon: 'dashboard' }, + { type: 'item', label: '_i.page.current.profile', path: this.#prefix + '/profile', icon: 'id_card' }, { type: 'item', label: '_i.page.current.settings', path: this.#prefix + '/settings', icon: 'settings' }, - { type: 'item', label: '_i.page.current.securitylog', path: this.#prefix + '/securitylogs', icon: 'badge' }, + { type: 'item', label: '_i.page.current.securitylog', path: this.#prefix + '/securitylogs', icon: 'security' }, { type: 'divider' }, { type: 'item', label: '_i.page.current.logout', path: this.#prefix + '/logout', icon: 'logout' }, ]; } -} \ No newline at end of file +} diff --git a/admin/src/pages/current/profile.tsx b/admin/src/pages/current/profile.tsx new file mode 100644 index 00000000..b8a087b0 --- /dev/null +++ b/admin/src/pages/current/profile.tsx @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 caixw +// +// SPDX-License-Identifier: MIT + +import { createEffect, JSX } from 'solid-js'; + +import { useApp, useOptions, User } from '@/app/context'; +import { buildEnumsOptions, Button, Choice, Divider, Form, FormAccessor, Page, Password, TextField } from '@/components'; +import { Sex, sexesMap } from '@/pages/admins/types'; + +export default function(): JSX.Element { + const opt = useOptions(); + const ctx = useApp(); + + const infoAccess = new FormAccessor({sex: 'unknown'}, ctx, 'PATCH', opt.api.info, async () => { + await ctx.refetchUser(); + }, (obj) => { + if (!obj.name) { + return new Map([['name', ctx.locale().t('_i.error.canNotBeEmpty')]]); + } + if (!obj.nickname) { + return new Map([['nickname', ctx.locale().t('_i.error.canNotBeEmpty')]]); + } + }); + + const nameA = infoAccess.accessor('name'); + const nicknameA = infoAccess.accessor('nickname'); + const sexA = infoAccess.accessor('sex'); + const avatarA = infoAccess.accessor('avatar'); + + createEffect(() => { + const u = ctx.user(); + if (!u) { return; } + + infoAccess.setPreset({ name: u.name, nickname: u.nickname, sex: u.sex }); + + nameA.setValue(u.name!); + nicknameA.setValue(u.nickname!); + sexA.setValue(u.sex!); + avatarA.setValue(u.avatar!); + }); + + return +
+ avatar +
+

{ctx.user()?.name}

+ +
+
+ + + +
+
+ + + + +
+ + +
+ + +
+ + +
+
; +} + +interface ProfilePassword { + old: string; + new: string; + confirm: string; +} + +function Pass(): JSX.Element { + const ctx = useApp(); + + const passAccess = new FormAccessor({ old: '', new: '', confirm: '' }, ctx, 'PUT', '/password', undefined, (obj)=>{ + if (obj.old === '') { + return new Map([['old', ctx.locale().t('_i.error.canNotBeEmpty')]]); + } + if (obj.new === '') { + return new Map([['new', ctx.locale().t('_i.error.canNotBeEmpty')]]); + } + + if (obj.old === obj.new) { + return new Map([['new', ctx.locale().t('_i.error.oldNewPasswordCanNotBeEqual')]]); + } + if (obj.new !== obj.confirm) { + return new Map([['confirm', ctx.locale().t('_i.error.newConfirmPasswordMustBeEqual')]]); + } + }); + + return
+ + + + +
+ +
+ ; +} diff --git a/admin/src/pages/current/settings.tsx b/admin/src/pages/current/settings.tsx index 7d57b0cf..1a00e7f3 100644 --- a/admin/src/pages/current/settings.tsx +++ b/admin/src/pages/current/settings.tsx @@ -4,9 +4,9 @@ import { JSX } from 'solid-js'; -import { Divider, Options, Choice, Label, FieldAccessor, RadioGroup, Page, Description } from '@/components'; -import { Mode, Scheme, Theme, Contrast, UnitStyle } from '@/core'; -import { useOptions, useApp } from '@/app/context'; +import { useApp, useOptions } from '@/app/context'; +import { Choice, Description, Divider, FieldAccessor, Options, Page, RadioGroup } from '@/components'; +import { Contrast, Mode, Scheme, Theme, UnitStyle } from '@/core'; export default function(): JSX.Element { const ctx = useApp(); @@ -40,9 +40,9 @@ export default function(): JSX.Element { {ctx.locale().t('_i.theme.system')}], - ['dark', ], - ['light', ] + ['system', ctx.locale().t('_i.theme.system')], + ['dark', ctx.locale().t('_i.theme.dark')], + ['light', ctx.locale().t('_i.theme.light')] ]} /> @@ -54,9 +54,9 @@ export default function(): JSX.Element { {ctx.locale().t('_i.theme.more')}], - ['nopreference', ], - ['less', ] + ['more', ctx.locale().t('_i.theme.more')], + ['nopreference', ctx.locale().t('_i.theme.nopreference')], + ['less', ctx.locale().t('_i.theme.less')] ]} /> @@ -85,9 +85,9 @@ export default function(): JSX.Element { {ctx.locale().t('_i.locale.narrow')}], - ['short', ], - ['full', ], + ['narrow', ctx.locale().t('_i.locale.narrow')], + ['short', ctx.locale().t('_i.locale.short')], + ['full', ctx.locale().t('_i.locale.long')], ]}/>
diff --git a/admin/src/pages/current/style.css b/admin/src/pages/current/style.css index 0b7a2648..2a4546d3 100644 --- a/admin/src/pages/current/style.css +++ b/admin/src/pages/current/style.css @@ -11,3 +11,17 @@ @apply flex justify-center flex-col px-2 gap-2 w-full xs:w-80; } } + +.p--profile { + .content { + @apply flex justify-between flex-col sm:flex-row box-border sm:gap-8 gap-10; + } + + .form { + @apply flex flex-col sm:w-[50%] w-full; + + .actions { + @apply w-full flex justify-end gap-5; + } + } +} diff --git a/admin/src/pages/roles/index.ts b/admin/src/pages/roles/index.ts index a8bb440b..0b57e843 100644 --- a/admin/src/pages/roles/index.ts +++ b/admin/src/pages/roles/index.ts @@ -40,7 +40,7 @@ export class roles implements Pages { menus(): Array { return [ - { type: 'item', label: '_i.page.roles.roles', path: this.#prefix }, + { type: 'item', icon: 'groups', label: '_i.page.roles.roles', path: this.#prefix }, ]; } } diff --git a/admin/src/pages/system/index.ts b/admin/src/pages/system/index.ts index 0334c235..8bc65bc1 100644 --- a/admin/src/pages/system/index.ts +++ b/admin/src/pages/system/index.ts @@ -2,11 +2,11 @@ // // SPDX-License-Identifier: MIT +import { MenuItem, Route } from '@/app/options/route'; +import { Pages } from '@/pages/pages'; import { default as APIs } from './apis'; -import { default as Services } from './services'; import { default as Info } from './info'; -import { Pages } from '@/pages/pages'; -import { MenuItem, Route } from '@/app/options/route'; +import { default as Services } from './services'; export class system implements Pages { static APIs = APIs; @@ -34,9 +34,9 @@ export class system implements Pages { menus(): Array { return [ - { type: 'item', label: '_i.page.system.apis', path: this.#prefix+'/apis' }, - { type: 'item', label: '_i.page.system.services', path: this.#prefix+'/services' }, - { type: 'item', label: '_i.page.system.info', path: this.#prefix+'/info' }, + { type: 'item', icon: 'api', label: '_i.page.system.apis', path: this.#prefix+'/apis' }, + { type: 'item', icon: 'settings_slow_motion', label: '_i.page.system.services', path: this.#prefix+'/services' }, + { type: 'item', icon: 'help', label: '_i.page.system.info', path: this.#prefix+'/info' }, ]; } -} \ No newline at end of file +} diff --git a/cmd/admin/src/main.tsx b/cmd/admin/src/main.tsx index 25980797..9ebb7a43 100644 --- a/cmd/admin/src/main.tsx +++ b/cmd/admin/src/main.tsx @@ -27,9 +27,9 @@ const routes: Routes = { ] }, private: { - home: '/current/home', + home: '/current/dashboard', routes: [ - { path: ['/dashboard', '/'], component: pages.current.Home }, + { path: ['/dashboard', '/'], component: pages.current.Dashboard }, { path: '/test', component: Test }, ...roles.routes(), ...admins.routes(), @@ -41,18 +41,18 @@ const routes: Routes = { }; const menus: Options['menus'] = [ - { type: 'item', label: 'home', path: '/current/home' }, + { type: 'item', icon: 'dashboard', label: '_i.page.current.dashboard', path: '/current/dashboard' }, { type: 'item', label: 'nest.abc', path: '/test' }, { type: 'group', label: 'system', items: [ { - type: 'item', label: 'administrator', items: [ + type: 'item', label: 'administrator', icon: 'admin_panel_settings', items: [ ...roles.menus(), ...admins.menus(), ] }, { - type: 'item', label: '_i.page.system.system', items: [ + type: 'item', label: '_i.page.system.system', icon: 'host', items: [ ...system.menus(), ] }