Browse Source

feat(setting): add personal setting

gemercheung 3 years ago
parent
commit
0dd7a4d8f8

+ 16 - 0
src/api/account/index.ts

@@ -0,0 +1,16 @@
+import { defHttp } from '/@/utils/http/axios';
+import { GetAccountInfoModel } from './model';
+
+enum Api {
+  ACCOUNT_INFO = '/account/getAccountInfo',
+  SESSION_TIMEOUT = '/user/sessionTimeout',
+  TOKEN_EXPIRED = '/user/tokenExpired',
+}
+
+// Get personal center-basic settings
+
+export const accountInfoApi = () => defHttp.get<GetAccountInfoModel>({ url: Api.ACCOUNT_INFO });
+
+export const sessionTimeoutApi = () => defHttp.post<void>({ url: Api.SESSION_TIMEOUT });
+
+export const tokenExpiredApi = () => defHttp.post<void>({ url: Api.TOKEN_EXPIRED });

+ 7 - 0
src/api/account/model.ts

@@ -0,0 +1,7 @@
+export interface GetAccountInfoModel {
+  email: string;
+  name: string;
+  introduction: string;
+  phone: string;
+  address: string;
+}

+ 15 - 5
src/layouts/default/header/components/user-dropdown/index.vue

@@ -11,12 +11,12 @@
 
     <template #overlay>
       <Menu @click="handleMenuClick">
-        <!-- <MenuItem
-          key="doc"
-          :text="t('layout.header.dropdownItemDoc')"
+        <MenuItem
+          key="setting"
+          :text="t('layout.header.personalSetting')"
           icon="ion:document-text-outline"
           v-if="getShowDoc"
-        /> -->
+        />
         <MenuDivider v-if="getShowDoc" />
         <MenuItem
           v-if="getUseLockPage"
@@ -51,10 +51,11 @@
   import headerImg from '/@/assets/images/header.jpg';
   import { propTypes } from '/@/utils/propTypes';
   import { openWindow } from '/@/utils';
+  import { useGo } from '/@/hooks/web/usePage';
 
   import { createAsyncComponent } from '/@/utils/factory/createAsyncComponent';
 
-  type MenuEvent = 'logout' | 'doc' | 'lock';
+  type MenuEvent = 'logout' | 'doc' | 'lock' | 'setting';
 
   export default defineComponent({
     name: 'UserDropdown',
@@ -73,6 +74,7 @@
       const { t } = useI18n();
       const { getShowDoc, getUseLockPage } = useHeaderSetting();
       const userStore = useUserStore();
+      const go = useGo();
 
       const getUserInfo = computed(() => {
         const { realName = '', avatar, desc } = userStore.getUserInfo || {};
@@ -95,6 +97,12 @@
         openWindow(DOC_URL);
       }
 
+      // go setting
+      function openSetting() {
+        // openWindow(DOC_URL);
+        go('/account');
+      }
+
       function handleMenuClick(e: { key: MenuEvent }) {
         switch (e.key) {
           case 'logout':
@@ -102,6 +110,8 @@
             break;
           case 'doc':
             openDoc();
+          case 'setting':
+            openSetting();
             break;
           case 'lock':
             handleLock();

+ 1 - 0
src/locales/lang/en/layout.ts

@@ -3,6 +3,7 @@ export default {
   header: {
     // user dropdown
     dropdownItemDoc: 'Document',
+    personalSetting: 'personal setting',
     dropdownItemLoginOut: 'Login Out',
 
     tooltipErrorLog: 'Error log',

+ 1 - 0
src/locales/lang/zh-CN/layout.ts

@@ -3,6 +3,7 @@ export default {
   header: {
     // user dropdown
     dropdownItemDoc: '文档',
+    personalSetting: '个人设置',
     dropdownItemLoginOut: '退出系统',
 
     // tooltip

+ 30 - 0
src/router/routes/modules/personalSetting.ts

@@ -0,0 +1,30 @@
+import type { AppRouteModule } from '/@/router/types';
+
+import { LAYOUT } from '/@/router/constant';
+import { t } from '/@/hooks/web/useI18n';
+
+const account: AppRouteModule = {
+  path: '/account',
+  name: 'Account',
+  component: LAYOUT,
+  redirect: '/account/index',
+  meta: {
+    icon: 'medical-icon:care-staff-area',
+    title: t('routes.demo.page.account'),
+    orderNo: 102,
+    hideChildrenInMenu: true,
+    hideMenu: true,
+  },
+  children: [
+    {
+      path: 'index',
+      name: 'AccountSettingPage',
+      component: () => import('/@/views/dashboard/setting/index.vue'),
+      meta: {
+        title: t('routes.demo.page.accountSetting'),
+      },
+    },
+  ],
+};
+
+export default account;

+ 1 - 0
src/views/dashboard/member/list.vue

@@ -86,6 +86,7 @@
         {
           title: '最后登录时间',
           dataIndex: 'lastLogin',
+          sorter: true,
           width: 120,
         },
         {

+ 59 - 0
src/views/dashboard/setting/AccountBind.vue

@@ -0,0 +1,59 @@
+<template>
+  <CollapseContainer title="账号绑定" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #avatar>
+              <Icon v-if="item.avatar" class="avatar" :icon="item.avatar" :color="item.color" />
+            </template>
+            <template #title>
+              {{ item.title }}
+              <a-button type="link" size="small" v-if="item.extra" class="extra">
+                {{ item.extra }}
+              </a-button>
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+  import Icon from '/@/components/Icon/index';
+
+  import { accountBindList } from './data';
+
+  export default defineComponent({
+    components: {
+      CollapseContainer,
+      List,
+      ListItem: List.Item,
+      ListItemMeta: List.Item.Meta,
+      Icon,
+    },
+    setup() {
+      return {
+        list: accountBindList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .avatar {
+    font-size: 40px !important;
+  }
+
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+    cursor: pointer;
+  }
+</style>

+ 95 - 0
src/views/dashboard/setting/BaseSetting.vue

@@ -0,0 +1,95 @@
+<template>
+  <CollapseContainer title="基本设置" :canExpan="false">
+    <a-row :gutter="24">
+      <a-col :span="14">
+        <BasicForm @register="register" />
+      </a-col>
+      <a-col :span="10">
+        <div class="change-avatar">
+          <div class="mb-2">头像</div>
+          <CropperAvatar
+            :uploadApi="uploadApi"
+            :value="avatar"
+            btnText="更换头像"
+            :btnProps="{ preIcon: 'ant-design:cloud-upload-outlined' }"
+            @change="updateAvatar"
+            width="150"
+          />
+        </div>
+      </a-col>
+    </a-row>
+    <Button type="primary" @click="handleSubmit"> 更新基本信息 </Button>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { Button, Row, Col } from 'ant-design-vue';
+  import { computed, defineComponent, onMounted } from 'vue';
+  import { BasicForm, useForm } from '/@/components/Form/index';
+  import { CollapseContainer } from '/@/components/Container';
+  import { CropperAvatar } from '/@/components/Cropper';
+
+  import { useMessage } from '/@/hooks/web/useMessage';
+
+  import headerImg from '/@/assets/images/header.jpg';
+  import { accountInfoApi } from '/@/api/account/index';
+  import { baseSetschemas } from './data';
+  import { useUserStore } from '/@/store/modules/user';
+  import { uploadApi } from '/@/api/sys/upload';
+
+  export default defineComponent({
+    components: {
+      BasicForm,
+      CollapseContainer,
+      Button,
+      ARow: Row,
+      ACol: Col,
+      CropperAvatar,
+    },
+    setup() {
+      const { createMessage } = useMessage();
+      const userStore = useUserStore();
+
+      const [register, { setFieldsValue }] = useForm({
+        labelWidth: 120,
+        schemas: baseSetschemas,
+        showActionButtonGroup: false,
+      });
+
+      onMounted(async () => {
+        const data = await accountInfoApi();
+        setFieldsValue(data);
+      });
+
+      const avatar = computed(() => {
+        const { avatar } = userStore.getUserInfo;
+        return avatar || headerImg;
+      });
+
+      function updateAvatar(src: string) {
+        const userinfo = userStore.getUserInfo;
+        userinfo.avatar = src;
+        userStore.setUserInfo(userinfo);
+      }
+
+      return {
+        avatar,
+        register,
+        uploadApi: uploadApi as any,
+        updateAvatar,
+        handleSubmit: () => {
+          createMessage.success('更新成功!');
+        },
+      };
+    },
+  });
+</script>
+
+<style lang="less" scoped>
+  .change-avatar {
+    img {
+      display: block;
+      margin-bottom: 15px;
+      border-radius: 50%;
+    }
+  }
+</style>

+ 53 - 0
src/views/dashboard/setting/MsgNotify.vue

@@ -0,0 +1,53 @@
+<template>
+  <CollapseContainer title="新消息通知" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #title>
+              {{ item.title }}
+              <Switch
+                class="extra"
+                checked-children="开"
+                un-checked-children="关"
+                default-checked
+              />
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List, Switch } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  import { msgNotifyList } from './data';
+
+  export default defineComponent({
+    components: {
+      CollapseContainer,
+      List,
+      ListItem: List.Item,
+      ListItemMeta: List.Item.Meta,
+      Switch,
+    },
+    setup() {
+      return {
+        list: msgNotifyList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+  }
+</style>

+ 47 - 0
src/views/dashboard/setting/SecureSetting.vue

@@ -0,0 +1,47 @@
+<template>
+  <CollapseContainer title="安全设置" :canExpan="false">
+    <List>
+      <template v-for="item in list" :key="item.key">
+        <ListItem>
+          <ListItemMeta>
+            <template #title>
+              {{ item.title }}
+              <div class="extra" v-if="item.extra">
+                {{ item.extra }}
+              </div>
+            </template>
+            <template #description>
+              <div>{{ item.description }}</div>
+            </template>
+          </ListItemMeta>
+        </ListItem>
+      </template>
+    </List>
+  </CollapseContainer>
+</template>
+<script lang="ts">
+  import { List } from 'ant-design-vue';
+  import { defineComponent } from 'vue';
+  import { CollapseContainer } from '/@/components/Container/index';
+
+  import { secureSettingList } from './data';
+
+  export default defineComponent({
+    components: { CollapseContainer, List, ListItem: List.Item, ListItemMeta: List.Item.Meta },
+    setup() {
+      return {
+        list: secureSettingList,
+      };
+    },
+  });
+</script>
+<style lang="less" scoped>
+  .extra {
+    float: right;
+    margin-top: 10px;
+    margin-right: 30px;
+    font-weight: normal;
+    color: #1890ff;
+    cursor: pointer;
+  }
+</style>

+ 149 - 0
src/views/dashboard/setting/data.ts

@@ -0,0 +1,149 @@
+import { FormSchema } from '/@/components/Form/index';
+
+export interface ListItem {
+  key: string;
+  title: string;
+  description: string;
+  extra?: string;
+  avatar?: string;
+  color?: string;
+}
+
+// tab的list
+export const settingList = [
+  {
+    key: '1',
+    name: '基本设置',
+    component: 'BaseSetting',
+  },
+  {
+    key: '2',
+    name: '安全设置',
+    component: 'SecureSetting',
+  },
+  {
+    key: '3',
+    name: '账号绑定',
+    component: 'AccountBind',
+  },
+  {
+    key: '4',
+    name: '新消息通知',
+    component: 'MsgNotify',
+  },
+];
+
+// 基础设置 form
+export const baseSetschemas: FormSchema[] = [
+  {
+    field: 'email',
+    component: 'Input',
+    label: '邮箱',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'name',
+    component: 'Input',
+    label: '昵称',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'introduction',
+    component: 'InputTextArea',
+    label: '个人简介',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'phone',
+    component: 'Input',
+    label: '联系电话',
+    colProps: { span: 18 },
+  },
+  {
+    field: 'address',
+    component: 'Input',
+    label: '所在地区',
+    colProps: { span: 18 },
+  },
+];
+
+// 安全设置 list
+export const secureSettingList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '当前密码强度::强',
+    extra: '修改',
+  },
+  {
+    key: '2',
+    title: '密保手机',
+    description: '已绑定手机::138****8293',
+    extra: '修改',
+  },
+  {
+    key: '3',
+    title: '密保问题',
+    description: '未设置密保问题,密保问题可有效保护账户安全',
+    extra: '修改',
+  },
+  {
+    key: '4',
+    title: '备用邮箱',
+    description: '已绑定邮箱::ant***sign.com',
+    extra: '修改',
+  },
+  {
+    key: '5',
+    title: 'MFA 设备',
+    description: '未绑定 MFA 设备,绑定后,可以进行二次确认',
+    extra: '修改',
+  },
+];
+
+// 账号绑定 list
+export const accountBindList: ListItem[] = [
+  {
+    key: '1',
+    title: '绑定淘宝',
+    description: '当前未绑定淘宝账号',
+    extra: '绑定',
+    avatar: 'ri:taobao-fill',
+    color: '#ff4000',
+  },
+  {
+    key: '2',
+    title: '绑定支付宝',
+    description: '当前未绑定支付宝账号',
+    extra: '绑定',
+    avatar: 'fa-brands:alipay',
+    color: '#2eabff',
+  },
+  {
+    key: '3',
+    title: '绑定钉钉',
+    description: '当前未绑定钉钉账号',
+    extra: '绑定',
+    avatar: 'ri:dingding-fill',
+    color: '#2eabff',
+  },
+];
+
+// 新消息通知 list
+export const msgNotifyList: ListItem[] = [
+  {
+    key: '1',
+    title: '账户密码',
+    description: '其他用户的消息将以站内信的形式通知',
+  },
+  {
+    key: '2',
+    title: '系统消息',
+    description: '系统消息将以站内信的形式通知',
+  },
+  {
+    key: '3',
+    title: '待办任务',
+    description: '待办任务将以站内信的形式通知',
+  },
+];

+ 61 - 0
src/views/dashboard/setting/index.vue

@@ -0,0 +1,61 @@
+<template>
+  <ScrollContainer>
+    <div ref="wrapperRef" :class="prefixCls">
+      <Tabs tab-position="left" :tabBarStyle="tabBarStyle">
+        <template v-for="item in settingList" :key="item.key">
+          <TabPane :tab="item.name">
+            <component :is="item.component" />
+          </TabPane>
+        </template>
+      </Tabs>
+    </div>
+  </ScrollContainer>
+</template>
+
+<script lang="ts">
+  import { defineComponent } from 'vue';
+  import { Tabs } from 'ant-design-vue';
+
+  import { ScrollContainer } from '/@/components/Container/index';
+  import { settingList } from './data';
+
+  import BaseSetting from './BaseSetting.vue';
+  import SecureSetting from './SecureSetting.vue';
+  import AccountBind from './AccountBind.vue';
+  import MsgNotify from './MsgNotify.vue';
+
+  export default defineComponent({
+    components: {
+      ScrollContainer,
+      Tabs,
+      TabPane: Tabs.TabPane,
+      BaseSetting,
+      SecureSetting,
+      AccountBind,
+      MsgNotify,
+    },
+    setup() {
+      return {
+        prefixCls: 'account-setting',
+        settingList,
+        tabBarStyle: {
+          width: '220px',
+        },
+      };
+    },
+  });
+</script>
+<style lang="less">
+  .account-setting {
+    margin: 12px;
+    background-color: @component-background;
+
+    .base-title {
+      padding-left: 0;
+    }
+
+    .ant-tabs-tab-active {
+      background-color: @item-active-bg;
+    }
+  }
+</style>