diff --git a/package.json b/package.json index 545d0e725..73fdd3dd1 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "html-to-md": "^0.8.8", "inquirer": "^8.2.0", "internal-ip": "^6.2.0", + "ionicons": "^8.1.0", "jquery": "^3.6.4", "jspdf": "^2.5.1", "less": "^4.1.2", diff --git a/resources/ai-kb/zh/howto/user-settings/profile.md b/resources/ai-kb/zh/howto/user-settings/profile.md index b713b1ba7..0ffb5bfb9 100644 --- a/resources/ai-kb/zh/howto/user-settings/profile.md +++ b/resources/ai-kb/zh/howto/user-settings/profile.md @@ -20,7 +20,7 @@ negative: - 邮箱在「个人设置」中只读,修改需走「修改邮箱」子页 - 部门信息只读,不可自助修改,需联系系统管理员调整 - LDAP 用户的昵称 / 头像 / 电话改动会同步回 LDAP,但 LDAP 拒收时本地也不会保存 -last_verified: v1.7.90 +last_verified: v1.10.12 --- # 修改个人资料 @@ -28,7 +28,7 @@ last_verified: v1.7.90 ## 入口 - 桌面端:右上角头像 →「设置」→「个人设置」 -- 移动端:底部「我的」→「设置」→「个人设置」 +- 移动端:底部「我的」→「设置」→ 点顶部个人资料卡(头像、昵称) - 后端接口:`POST api/users/editdata` ## 可改字段与约束 diff --git a/resources/ai-kb/zh/menu-map/user-settings/entry.md b/resources/ai-kb/zh/menu-map/user-settings/entry.md index e9de9a1bb..5001dce06 100644 --- a/resources/ai-kb/zh/menu-map/user-settings/entry.md +++ b/resources/ai-kb/zh/menu-map/user-settings/entry.md @@ -18,7 +18,7 @@ prerequisites: [] negative: - 个人设置没有移动端 Tabbar 一级入口,需先进「我的」再进设置 - 未登录用户没有「个人设置」入口 -last_verified: v1.9.66 +last_verified: v1.10.12 --- # 个人设置入口 @@ -32,7 +32,7 @@ last_verified: v1.9.66 - 移动端:底部 Tabbar「我的」→「设置」 - 路由:`/manage/setting/personal`(默认子页),其他子页为 `/manage/setting/` -## 子页清单(按菜单顺序) +## 子页清单(桌面端菜单顺序) - 个人设置(资料)→ [[user-settings.profile.howto]] - 密码设置 → [[user-settings.password.howto]] @@ -44,6 +44,14 @@ last_verified: v1.9.66 - 系统更新(仅系统管理员可见,有系统新版本时显示新版本号)→ [[system-update.online-update.howto]] - 更新日志(桌面端有新版本时显示为「更新客户端」,点击打开客户端更新弹窗) / 版本(新版移动端 App 右侧有「检查更新」,见 [[mobile-client.update.howto]]) / 登录设备 / 清除缓存 / 退出登录 +## 移动端(竖屏)菜单布局 + +移动端设置页与桌面端子页相同,但排版不同: + +- 顶部为个人资料卡(头像、昵称、邮箱),点击进入「个人设置」 +- 其余项带图标与右箭头,按组排列:密码设置 / 修改邮箱 / 登录设备(右侧显示设备数)→ 语言设置 / 主题设置(右侧显示当前语言、主题)/ 键盘设置 → 系统设置 / License Key / 系统更新(仅管理员)→ 版本 / 更新日志 / 隐私政策 → 清除缓存 / 删除帐号(红色) +- 「退出登录」为列表最底部单独的红色按钮 + ## 权限要求 - 所有登录用户可见 diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js index bd8b43adb..78c0c117c 100644 --- a/resources/assets/js/app.js +++ b/resources/assets/js/app.js @@ -43,6 +43,7 @@ import TagInput from './components/TagInput.vue' import TableAction from './components/TableAction.vue' import QuickEdit from './components/QuickEdit.vue' import UserAvatar from './components/UserAvatar' +import IonIcon from './components/IonIcon.vue' import Imgs from './components/Replace/Imgs' import ImgView from './components/ImgView.vue' import Scrollbar from './components/Scrollbar' @@ -54,6 +55,7 @@ Vue.component('TagInput', TagInput) Vue.component('TableAction', TableAction); Vue.component('QuickEdit', QuickEdit); Vue.component('UserAvatar', UserAvatar); +Vue.component('IonIcon', IonIcon); Vue.component('Imgs', Imgs); Vue.component('ImgView', ImgView); Vue.component('Scrollbar', Scrollbar); diff --git a/resources/assets/js/components/IonIcon.vue b/resources/assets/js/components/IonIcon.vue new file mode 100644 index 000000000..5c63e98d5 --- /dev/null +++ b/resources/assets/js/components/IonIcon.vue @@ -0,0 +1,31 @@ + diff --git a/resources/assets/js/pages/manage/setting/index.vue b/resources/assets/js/pages/manage/setting/index.vue index 0bc4104e0..6042307fb 100644 --- a/resources/assets/js/pages/manage/setting/index.vue +++ b/resources/assets/js/pages/manage/setting/index.vue @@ -11,12 +11,21 @@
+
+ +
+
{{userInfo.nickname}}
+
{{userInfo.email}}
+
+ +
  • + + {{$L(item.name)}} +
+
{{$L('退出登录')}}
@@ -55,6 +72,24 @@ import {mapState} from "vuex"; import axios from "axios"; import MobileNavTitle from "../../../components/Mobile/NavTitle.vue"; import emitter from "../../../store/events"; +import {languageList, languageName} from "../../../i18n"; +import { + chevronForward, + contrastOutline, + documentTextOutline, + informationCircleOutline, + keyOutline, + keypadOutline, + languageOutline, + lockClosedOutline, + mailOutline, + personRemoveOutline, + phonePortraitOutline, + settingsOutline, + shieldCheckmarkOutline, + syncOutline, + trashOutline, +} from "ionicons/icons"; export default { components: {MobileNavTitle}, @@ -64,6 +99,7 @@ export default { serverVersion: null, clientVersion: window.systemInfo.version, canCheckUpdate: $A.eeuiAppIsExpo(), + chevronForward, } }, @@ -78,7 +114,7 @@ export default { }, computed: { - ...mapState(['userInfo', 'userIsAdmin', 'clientNewVersion', 'systemNewVersion', 'systemConfig']), + ...mapState(['userId', 'userInfo', 'userIsAdmin', 'clientNewVersion', 'systemNewVersion', 'systemConfig', 'themeConf', 'themeList']), showContent() { return this.$route.path.match(/^\/manage\/setting\/[\w-]+$/) @@ -123,6 +159,49 @@ export default { return menu; }, + /** + * 移动端菜单:个人设置移至顶部资料卡、退出登录改为底部按钮,其余按语义重新分组 + */ + mobileMenu() { + const theme = this.themeList.find(item => item.value === this.themeConf) + const groups = [ + [ + {path: 'password', name: '密码设置', icon: lockClosedOutline}, + {path: 'email', name: '修改邮箱', icon: mailOutline}, + {path: 'device', name: '登录设备', icon: phonePortraitOutline}, + ], + [ + {path: 'language', name: '语言设置', icon: languageOutline, value: languageList[languageName]}, + {path: 'theme', name: '主题设置', icon: contrastOutline, value: theme ? this.$L(theme.name) : ''}, + (this.$Electron || this.$isEEUIApp) && {path: 'keyboard', name: '键盘设置', icon: keypadOutline}, + ], + this.userIsAdmin && [ + {path: 'system', name: '系统设置', icon: settingsOutline}, + {path: 'license', name: 'License Key', icon: keyOutline}, + {path: 'system-update', name: '系统更新', icon: syncOutline}, + ], + [ + {path: 'version-show', name: '版本', icon: informationCircleOutline}, + {path: 'version', name: this.clientNewVersion ? '更新客户端' : '更新日志', title: '更新日志', icon: documentTextOutline}, + $A.isDooServer() && this.$isEEUIApp && {path: 'privacy', name: '隐私政策', icon: shieldCheckmarkOutline}, + ], + [ + {path: 'clearCache', name: '清除缓存', icon: trashOutline}, + $A.isDooServer() && this.$isEEUIApp && {path: 'delete', name: '删除帐号', icon: personRemoveOutline, danger: true}, + ], + ] + const menu = [] + groups.filter(Boolean).forEach(group => { + group.filter(Boolean).forEach((item, index) => { + menu.push(Object.assign(item, { + divided: index === 0, + arrow: item.path !== 'version-show', + })) + }) + }) + return menu + }, + titleNameRoute() { const {routeName, menu} = this; let name = ''; @@ -219,11 +298,12 @@ export default { } }, - classNameRoute(path, divided) { + classNameRoute(path, divided, danger) { return { "flex": true, "active": this.windowLandscape && this.routeName === `manage-setting-${path}`, - "divided": !!divided + "divided": !!divided, + "danger": !!danger }; }, diff --git a/resources/assets/sass/components/_.scss b/resources/assets/sass/components/_.scss index d7b1883b1..81ff49c26 100644 --- a/resources/assets/sass/components/_.scss +++ b/resources/assets/sass/components/_.scss @@ -4,6 +4,7 @@ @import "float-spinner"; @import "gantt-view"; @import "img-update"; +@import "ion-icon"; @import "loading"; @import "mobile"; @import "scroller-y"; diff --git a/resources/assets/sass/components/ion-icon.scss b/resources/assets/sass/components/ion-icon.scss new file mode 100644 index 000000000..1d8cdf78d --- /dev/null +++ b/resources/assets/sass/components/ion-icon.scss @@ -0,0 +1,25 @@ +// 与 ionicons 官方 样式保持一致(见 components/IonIcon.vue) +.ion-icon { + display: inline-block; + width: 1em; + height: 1em; + font-style: normal; + line-height: 1; + vertical-align: middle; + fill: currentColor; + + > svg { + display: block; + width: 100%; + height: 100%; + stroke: currentColor; + } + + .ionicon-fill-none { + fill: none; + } + + .ionicon-stroke-width { + stroke-width: 32px; + } +} diff --git a/resources/assets/sass/pages/page-setting.scss b/resources/assets/sass/pages/page-setting.scss index 0bc614a01..be019262b 100755 --- a/resources/assets/sass/pages/page-setting.scss +++ b/resources/assets/sass/pages/page-setting.scss @@ -553,40 +553,103 @@ body.window-portrait { width: 100%; height: 100%; z-index: 1; + .setting-profile { + display: flex; + align-items: center; + padding: 18px 20px; + background-color: #ffffff; + .setting-profile-info { + flex: 1; + min-width: 0; + margin-left: 14px; + .setting-profile-name { + font-size: 17px; + font-weight: 500; + line-height: 24px; + color: $primary-title-color; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .setting-profile-email { + margin-top: 2px; + font-size: 13px; + line-height: 18px; + color: $primary-desc-color; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + .setting-menu-arrow { + flex-shrink: 0; + margin-left: auto; + padding-left: 6px; + font-size: 18px; + color: #c5c8ce; + } > ul { - // 沉浸底部安全区(见 App.vue immersiveNavigation) - padding: 0 0 calc(20px + var(--navigation-bar-height)); + padding: 0; > li { position: relative; - padding: 0 32px; - line-height: 48px; + padding: 0 20px; + line-height: 52px; margin: 0; + font-size: 15px; + color: $primary-title-color; background-color: #ffffff; &:after { content: ""; position: absolute; top: 0; - left: 32px; + left: 56px; right: 0; z-index: 1; height: 1px; transform: scaleY(0.5); background-color: #F4F4F5; } - &:first-child { - &:after { - display: none; - } - } &.divided { - margin-top: 20px; + margin-top: 10px; + padding-top: 0; &:before, &:after { display: none; } } + &.danger { + color: #ed4014; + .setting-menu-icon { + color: #ed4014; + } + } + .setting-menu-icon { + flex-shrink: 0; + width: 22px; + margin-right: 14px; + font-size: 22px; + color: $primary-text-color; + } + .setting-menu-value, + .op-8 { + flex-shrink: 0; + margin-left: 12px; + color: $primary-desc-color; + opacity: 1; + } } } + .setting-logout { + margin-top: 10px; + // 沉浸底部安全区(见 App.vue immersiveNavigation) + margin-bottom: calc(20px + var(--navigation-bar-height)); + line-height: 52px; + font-size: 15px; + text-align: center; + color: #ed4014; + background-color: #ffffff; + } } .setting-content { position: absolute;