mirror of
https://github.com/kuaifan/dootask.git
synced 2026-10-29 22:10:42 +00:00
feat(setting): 移动端设置页改版,顶部个人资料卡、图标与右箭头、右侧显示当前值、按语义分组,退出登录改为底部按钮
- 引入 ionicons 8 新版图标,新增全局 IonIcon 组件(按需引入 SVG 内联渲染,不走 CDN) - 桌面端设置菜单保持不变 - 同步 ai-kb 设置入口与个人设置路径
This commit is contained in:
parent
1fb1062056
commit
58a0180caa
@ -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",
|
||||
|
||||
@ -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`
|
||||
|
||||
## 可改字段与约束
|
||||
|
||||
@ -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/<path>`
|
||||
|
||||
## 子页清单(按菜单顺序)
|
||||
## 子页清单(桌面端菜单顺序)
|
||||
|
||||
- 个人设置(资料)→ [[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 / 系统更新(仅管理员)→ 版本 / 更新日志 / 隐私政策 → 清除缓存 / 删除帐号(红色)
|
||||
- 「退出登录」为列表最底部单独的红色按钮
|
||||
|
||||
## 权限要求
|
||||
|
||||
- 所有登录用户可见
|
||||
|
||||
2
resources/assets/js/app.js
vendored
2
resources/assets/js/app.js
vendored
@ -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);
|
||||
|
||||
31
resources/assets/js/components/IonIcon.vue
Normal file
31
resources/assets/js/components/IonIcon.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script>
|
||||
/**
|
||||
* Ionicons(新版)SVG 图标
|
||||
*
|
||||
* 用法:
|
||||
* import {lockClosedOutline} from "ionicons/icons";
|
||||
* <IonIcon :icon="lockClosedOutline"/>
|
||||
*
|
||||
* 按需引入 ionicons/icons 导出的 data URI,内联渲染为 SVG(不使用 <ion-icon> 组件,避免运行时从 CDN 加载)
|
||||
* 尺寸跟随 font-size,颜色跟随 color
|
||||
*/
|
||||
export default {
|
||||
name: 'IonIcon',
|
||||
functional: true,
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
},
|
||||
render(h, {props, data}) {
|
||||
const {icon} = props
|
||||
return h('i', Object.assign({}, data, {
|
||||
staticClass: ['ion-icon', data.staticClass].filter(Boolean).join(' '),
|
||||
domProps: {
|
||||
innerHTML: icon.slice(icon.indexOf(',') + 1)
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -11,12 +11,21 @@
|
||||
<div class="setting-box">
|
||||
<div class="setting-menu">
|
||||
<MobileNavTitle :title="$L('设置')"/>
|
||||
<div v-if="windowPortrait" class="setting-profile" @click="toggleRoute('personal')">
|
||||
<UserAvatar :userid="userId" :size="52" :show-state-dot="false"/>
|
||||
<div class="setting-profile-info">
|
||||
<div class="setting-profile-name">{{userInfo.nickname}}</div>
|
||||
<div class="setting-profile-email">{{userInfo.email}}</div>
|
||||
</div>
|
||||
<IonIcon class="setting-menu-arrow" :icon="chevronForward"/>
|
||||
</div>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(item, key) in menu"
|
||||
v-for="(item, key) in (windowPortrait ? mobileMenu : menu)"
|
||||
:key="key"
|
||||
:class="classNameRoute(item.path, item.divided)"
|
||||
:class="classNameRoute(item.path, item.divided, item.danger)"
|
||||
@click="toggleRoute(item.path)">
|
||||
<IonIcon v-if="item.icon" class="setting-menu-icon" :icon="item.icon"/>
|
||||
<template v-if="item.path === 'device'">
|
||||
<AutoTip>{{$L(item.name)}}</AutoTip>
|
||||
<span v-if="deviceCount > 0" class="op-8">{{deviceCount}}</span>
|
||||
@ -30,12 +39,20 @@
|
||||
<Badge v-if="!!systemNewVersion" :text="systemNewVersion"/>
|
||||
</template>
|
||||
<template v-else-if="item.path === 'version-show'">
|
||||
<AutoTip>{{$L(item.name)}}: {{clientVersion}}</AutoTip>
|
||||
<AutoTip v-if="windowPortrait">{{$L(item.name)}}</AutoTip>
|
||||
<AutoTip v-else>{{$L(item.name)}}: {{clientVersion}}</AutoTip>
|
||||
<span v-if="windowPortrait" class="setting-menu-value">{{clientVersion}}</span>
|
||||
<span v-if="canCheckUpdate" class="check-update" @click.stop="onCheckUpdate">{{$L('检查更新')}}</span>
|
||||
</template>
|
||||
<template v-else-if="item.value">
|
||||
<AutoTip>{{$L(item.name)}}</AutoTip>
|
||||
<span class="setting-menu-value">{{item.value}}</span>
|
||||
</template>
|
||||
<span v-else>{{$L(item.name)}}</span>
|
||||
<IonIcon v-if="item.arrow" class="setting-menu-arrow" :icon="chevronForward"/>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-if="windowPortrait" class="setting-logout" @click="toggleRoute('logout')">{{$L('退出登录')}}</div>
|
||||
</div>
|
||||
<transition :name="$isEEUIApp ? 'mobile-dialog' : 'none'">
|
||||
<div v-if="showContent" class="setting-content">
|
||||
@ -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
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
1
resources/assets/sass/components/_.scss
vendored
1
resources/assets/sass/components/_.scss
vendored
@ -4,6 +4,7 @@
|
||||
@import "float-spinner";
|
||||
@import "gantt-view";
|
||||
@import "img-update";
|
||||
@import "ion-icon";
|
||||
@import "loading";
|
||||
@import "mobile";
|
||||
@import "scroller-y";
|
||||
|
||||
25
resources/assets/sass/components/ion-icon.scss
vendored
Normal file
25
resources/assets/sass/components/ion-icon.scss
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// 与 ionicons 官方 <ion-icon> 样式保持一致(见 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;
|
||||
}
|
||||
}
|
||||
85
resources/assets/sass/pages/page-setting.scss
vendored
85
resources/assets/sass/pages/page-setting.scss
vendored
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user