فهرست منبع

feat:个人中心

lizhi 3 ماه پیش
والد
کامیت
e2986fb2b4

+ 51 - 3
protected/controllers/CommonController.php

@@ -13,6 +13,7 @@ class CommonController extends Controller
 	public function actionUploadImg()
     {
         $upType = '';
+        $maxSize = 3;
         if (!empty($_FILES['follow'])) {
             $upType = 'follow';
             $upArr = $_FILES['follow'];
@@ -22,6 +23,7 @@ class CommonController extends Controller
         } elseif (!empty($_FILES['avatar'])) {
             $upType = 'avatar';
             $upArr = $_FILES['avatar'];
+            $maxSize = 0.3;
         } else {
             Helper::error('上传有误');
         }
@@ -29,12 +31,20 @@ class CommonController extends Controller
         if (!Helper::hasAnyString($type, ['png', 'jpeg', 'jpg'])) {
             Helper::error('图片格式不正确 ' . $type);
         }
-        if ($upArr['size'] > 1024 * 1024 * 3) {
-            Helper::error('图片大小不能超过3M');
+        if ($upArr['size'] > $maxSize * 1024 * 1024) {
+            Helper::error("图片大小不能超过{$maxSize}M");
         }
         $ext = strtolower(pathinfo($upArr['name'], PATHINFO_EXTENSION));
         $upPath = "zqcrm/{$upType}/" . date('Ymd') . '/' . Helper::getRandomString(16) . '.' . $ext;
-        Helper::imageUpload($upArr['tmp_name'], $upPath);
+        $res = Helper::imageUpload($upArr['tmp_name'], $upPath);
+        if (empty($res['code']) || $res['code'] != 200) {
+            Helper::error($res['msg'] ?? '上传出错');
+        }
+        if ($upType == 'avatar') {
+            $info = DB::getInfoById('useradmin', \Yii::app()->user->_id);
+            Helper::imageDelete($info['avatar']);
+            DB::updateById('useradmin', ['avatar' => $upPath], \Yii::app()->user->_id);
+        }
         if ($upType == 'editor') {
             exit(json_encode([
                 'errno' => 0,
@@ -56,6 +66,44 @@ class CommonController extends Controller
         Helper::dealCommonResult(Helper::imageDelete($path));
     }
 
+    public function actionChangePassword()
+    {
+        $old = Helper::getPostString('password');
+        $new = Helper::getPostString('newPassword');
+        $new1 = Helper::getPostString('confirmPassword');
+        if (!$old || !$new) {
+            Helper::error('参数错误');
+        }
+        if ( $new != $new1){
+            Helper::error('新密码不一致');
+        }
+        $info = DB::getInfoById('useradmin', \Yii::app()->user->_id);
+        if (!$info) {
+            Helper::error('用户未找到');
+        }
+        if (md5($old) != $info['password']) {
+            Helper::error('旧密码错误');
+        }
+        DB::updateById('useradmin', ['password' => md5($new)], \Yii::app()->user->_id);
+        Helper::ok();
+    }
+
+    public function actionEditUser()
+    {
+        $info = [
+            'username' => Helper::getPostString('username'),
+            'phone' => Helper::getPostString('phone'),
+            'email' => Helper::getPostString('email'),
+            'descr' => Helper::getPostString('descr'),
+            'sex' => Helper::getPostInt('sex'),
+        ];
+        if (!Helper::checkEmptyKey($info, ['username', 'phone', 'email'])) {
+            Helper::error('参数错误');
+        }
+        DB::updateById('useradmin', $info, \Yii::app()->user->_id);
+        Helper::ok();
+    }
+
     /**
      * Logs out the current user and redirect to homepage.
      */

+ 16 - 0
web/src/api/commonApi.ts

@@ -20,6 +20,22 @@ export class commonApi {
     })
   }
 
+  static changePassword(params: Form.ChangePassword) {
+    return request.post<Api.Http.BaseResponse>({
+      url: 'common/changePassword',
+      params
+      // showErrorMessage: false // 不显示错误消息
+    })
+  }
+
+  static editUSer(params: Form.User) {
+    return request.post<Api.Http.BaseResponse>({
+      url: 'common/editUSer',
+      params
+      // showErrorMessage: false // 不显示错误消息
+    })
+  }
+
   // 发送验证码
   static sendCode(phone: string) {
     const params = { phone }

BIN
web/src/assets/img/avatar/avatar.webp


+ 0 - 8
web/src/components/core/layouts/art-header-bar/index.vue

@@ -157,14 +157,6 @@
                     <i class="menu-icon iconfont-sys">&#xe734;</i>
                     <span class="menu-txt">{{ $t('topBar.user.userCenter') }}</span>
                   </li>
-                  <li @click="toDocs()">
-                    <i class="menu-icon iconfont-sys" style="font-size: 15px">&#xe828;</i>
-                    <span class="menu-txt">{{ $t('topBar.user.docs') }}</span>
-                  </li>
-                  <li @click="toGithub()">
-                    <i class="menu-icon iconfont-sys">&#xe8d6;</i>
-                    <span class="menu-txt">{{ $t('topBar.user.github') }}</span>
-                  </li>
                   <li @click="lockScreen()">
                     <i class="menu-icon iconfont-sys">&#xe817;</i>
                     <span class="menu-txt">{{ $t('topBar.user.lockScreen') }}</span>

+ 1 - 0
web/src/components/custom/FollowDialog.vue

@@ -33,6 +33,7 @@
             :action="uploadServer"
             list-type="picture-card"
             :limit="12"
+            :show-file-list="false"
             :on-preview="handlePictureCardPreview"
             :on-remove="handleRemove"
             :before-upload="beforeUpload"

+ 11 - 0
web/src/router/routes/asyncRoutes.ts

@@ -200,6 +200,17 @@ export const asyncRoutes: AppRouteRecord[] = [
         }
       },
       {
+        path: 'user-center',
+        name: 'userCenter',
+        component: RoutesAlias.UserCenter,
+        meta: {
+          title: 'menus.system.userCenter',
+          isHide: true,
+          keepAlive: false,
+          activePath: '/system/user' // 激活菜单路径
+        }
+      },
+      {
         id: 1102,
         path: 'role',
         name: 'Role',

+ 0 - 1
web/src/router/routesAlias.ts

@@ -13,7 +13,6 @@ export enum RoutesAlias {
   User = '/system/user', // 账户
   Role = '/system/role', // 角色
   UserCenter = '/system/user-center', // 用户中心
-  Menu = '/system/menu', // 菜单
   SchoolList = '/school/list', // 学校列表
   SchoolInfo = '/school/info', // 学校详情
   SchoolEdit = '/school/edit', // 编辑学校

+ 5 - 0
web/src/store/modules/user.ts

@@ -49,6 +49,10 @@ export const useUserStore = defineStore(
       info.value = newInfo
     }
 
+  const setUserForm = (newInfo: Form.User) => {
+      Object.assign(info.value, newInfo)
+  }
+
     /**
      * 设置登录状态
      * @param status 登录状态
@@ -153,6 +157,7 @@ export const useUserStore = defineStore(
       getSettingState,
       getWorktabState,
       setUserInfo,
+      setUserForm,
       setLoginStatus,
       setLanguage,
       setSearchHistory,

+ 14 - 0
web/src/typings/form.d.ts

@@ -13,12 +13,26 @@ declare namespace Form {
     descr: string
   }
 
+  interface User {
+    username: string
+    sex: number
+    descr: string
+    email: string
+    phone: string
+  }
+
   interface FindPassword {
     phone: string
     code: string
     password: string
   }
 
+  interface ChangePassword {
+    password: string
+    newPassword: string
+    confirmPassword: string
+  }
+
   interface UpdateAttr {
     id: number
     attr: string

+ 106 - 54
web/src/views/system/user-center/index.vue

@@ -4,37 +4,47 @@
       <div class="left-wrap">
         <div class="user-wrap box-style">
           <img class="bg" src="@imgs/user/bg.webp" />
-          <img class="avatar" src="@imgs/user/avatar.webp" />
+          <el-upload
+              :file-list="[]"
+              class="upload-demo"
+              name="avatar"
+              accept="jpg,jpeg,png,webp"
+              :action="uploadServer"
+              :headers="{Authorization: useUserStore().accessToken}"
+              :on-success="handleSuccess"
+          >
+            <img class="avatar" :src="userInfo.avatar || '@imgs/user/avatar.webp'" />
+          </el-upload>
           <h2 class="name">{{ userInfo.username }}</h2>
-          <p class="des">逐趣CRM 是一款漂亮的后台管理系统模版.</p>
+<!--          <p class="des">逐趣CRM 是一款漂亮的后台管理系统模版.</p>-->
 
           <div class="outer-info">
             <div>
-              <i class="iconfont-sys">&#xe72e;</i>
-              <span>jdkjjfnndf@mall.com</span>
+              <i class="iconfont-sys">&#xe608;</i>
+              <span>{{ form.sex ? '女' : '男' }}</span>
             </div>
             <div>
-              <i class="iconfont-sys">&#xe608;</i>
-              <span>交互专家</span>
+              <i class="iconfont-sys">&#xe6f4;</i>
+              <span>{{ form.phone }}</span>
             </div>
             <div>
-              <i class="iconfont-sys">&#xe736;</i>
-              <span>广东省深圳市</span>
+              <i class="iconfont-sys">&#xe72e;</i>
+              <span>{{ form.email }}</span>
             </div>
             <div>
               <i class="iconfont-sys">&#xe811;</i>
-              <span>字节跳动-某某平台部-UED</span>
+              <span>{{ form.descr}}</span>
             </div>
           </div>
 
-          <div class="lables">
+<!--          <div class="lables">
             <h3>标签</h3>
             <div>
               <div v-for="item in lableList" :key="item">
                 {{ item }}
               </div>
             </div>
-          </div>
+          </div>-->
         </div>
 
         <!-- <el-carousel class="gallery" height="160px"
@@ -59,8 +69,8 @@
             label-position="top"
           >
             <ElRow>
-              <ElFormItem label="名" prop="realName">
-                <el-input v-model="form.realName" :disabled="!isEdit" />
+              <ElFormItem label="用户名" prop="realName">
+                <el-input v-model="form.username" :disabled="!isEdit" />
               </ElFormItem>
               <ElFormItem label="性别" prop="sex" class="right-input">
                 <ElSelect v-model="form.sex" placeholder="Select" :disabled="!isEdit">
@@ -75,25 +85,16 @@
             </ElRow>
 
             <ElRow>
-              <ElFormItem label="昵称" prop="nikeName">
-                <ElInput v-model="form.nikeName" :disabled="!isEdit" />
+              <ElFormItem label="手机" prop="phone">
+                <ElInput v-model="form.phone" :disabled="!isEdit" />
               </ElFormItem>
               <ElFormItem label="邮箱" prop="email" class="right-input">
                 <ElInput v-model="form.email" :disabled="!isEdit" />
               </ElFormItem>
             </ElRow>
 
-            <ElRow>
-              <ElFormItem label="手机" prop="mobile">
-                <ElInput v-model="form.mobile" :disabled="!isEdit" />
-              </ElFormItem>
-              <ElFormItem label="地址" prop="address" class="right-input">
-                <ElInput v-model="form.address" :disabled="!isEdit" />
-              </ElFormItem>
-            </ElRow>
-
-            <ElFormItem label="个人介绍" prop="des" :style="{ height: '130px' }">
-              <ElInput type="textarea" :rows="4" v-model="form.des" :disabled="!isEdit" />
+            <ElFormItem label="个人介绍" prop="des">
+              <ElInput type="text" v-model="form.descr" :disabled="!isEdit" />
             </ElFormItem>
 
             <div class="el-form-item-right">
@@ -107,7 +108,7 @@
         <div class="info box-style" style="margin-top: 20px">
           <h1 class="title">更改密码</h1>
 
-          <ElForm :model="pwdForm" class="form" label-width="86px" label-position="top">
+          <ElForm ref="pwdFormRef" :model="pwdForm" :rules="pwdRules" class="form" label-width="86px" label-position="top">
             <ElFormItem label="当前密码" prop="password">
               <ElInput
                 v-model="pwdForm.password"
@@ -149,56 +150,77 @@
 
 <script setup lang="ts">
   import { useUserStore } from '@/store/modules/user'
-  import { ElForm, FormInstance, FormRules } from 'element-plus'
+  import {ElForm, ElMessage, FormInstance, FormRules, UploadProps} from 'element-plus'
+  import {commonApi} from "@/api/commonApi";
+  import {UserService} from "@/api/usersApi";
+  import {computed} from "vue";
 
   defineOptions({ name: 'UserCenter' })
 
+  const uploadServer = computed(() => import.meta.env.VITE_API_URL + import.meta.env.VITE_UPLOAD_URL)
   const userStore = useUserStore()
   const userInfo = computed(() => userStore.getUserInfo)
 
   const isEdit = ref(false)
   const isEditPwd = ref(false)
   const date = ref('')
-  const form = reactive({
-    realName: 'John Snow',
-    nikeName: '皮卡丘',
-    email: '59301283@mall.com',
-    mobile: '18888888888',
-    address: '广东省深圳市宝安区西乡街道101栋201',
-    sex: '2',
-    des: '逐趣CRM 是一款漂亮的后台管理系统模版.'
+  const form = reactive<Form.User>({
+    username: userInfo.value.username || '',
+    email: userInfo.value.email || '',
+    phone: userInfo.value.phone || '',
+    sex: userInfo.value.sex || 0,
+    descr: userInfo.value.descr || '',
   })
 
-  const pwdForm = reactive({
-    password: '123456',
-    newPassword: '123456',
-    confirmPassword: '123456'
+  const handleSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
+    if (response.code === 200) {
+      ElMessage.success('上传成功!')
+      userInfo.value.avatar = response.data.url
+    } else {
+      ElMessage.error(response.msg)
+    }
+  }
+
+  const pwdForm = reactive<Form.ChangePassword>({
+    password: '',
+    newPassword: '',
+    confirmPassword: ''
   })
 
   const ruleFormRef = ref<FormInstance>()
+  const pwdFormRef = ref<FormInstance>()
+
+  const pwdRules = reactive<FormRules>({
+    password: [
+      {required: true, message: '请输入密码', trigger: 'blur'},
+      {min: 1, max: 12, message: '长度在 1 到 12 个字符', trigger: 'blur'}
+    ],
+    newPassword: [
+      {required: true, message: '请输入新密码', trigger: 'blur'},
+      {min: 6, max: 12, message: '长度在 6 到 12 个字符', trigger: 'blur'}
+    ],
+    confirmPassword: [{required: true, message: '请确认新密码', trigger: 'blur'}],
+  })
 
   const rules = reactive<FormRules>({
-    realName: [
-      { required: true, message: '请输入昵称', trigger: 'blur' },
-      { min: 2, max: 50, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+    username: [
+      { required: true, message: '请输入用户名', trigger: 'blur' },
+      { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
     ],
-    nikeName: [
-      { required: true, message: '请输入昵称', trigger: 'blur' },
-      { min: 2, max: 50, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+    descr: [
+      { max: 20, message: '长度最多20个字符', trigger: 'blur' }
     ],
-    email: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
-    mobile: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
-    address: [{ required: true, message: '请输入地址', trigger: 'blur' }],
-    sex: [{ type: 'array', required: true, message: '请选择性别', trigger: 'blur' }]
+    phone: [{ required: true, message: '请输入手机号码', trigger: 'blur' }],
+    email: [{ type: 'email', message: '请输入正确邮箱', trigger: 'blur' }],
   })
 
   const options = [
     {
-      value: '1',
+      value: 0,
       label: '男'
     },
     {
-      value: '2',
+      value: 1,
       label: '女'
     }
   ]
@@ -231,12 +253,42 @@
     date.value = text
   }
 
-  const edit = () => {
+  const edit = async () => {
     isEdit.value = !isEdit.value
+    if (!isEdit.value) {
+      if (!ruleFormRef.value) return
+      await ruleFormRef.value.validate((valid) => {
+        if (valid) {
+          commonApi.editUSer(form)
+              .then(() => {
+                ElMessage.success('修改成功')
+                userStore.setUserForm(form)
+              })
+              .catch(() => {
+                ElMessage.error('操作失败')
+              })
+        }
+      })
+    }
   }
 
-  const editPwd = () => {
+  const editPwd = async () => {
     isEditPwd.value = !isEditPwd.value
+    if (!isEditPwd.value) {
+      if (!pwdFormRef.value) return
+      // 保存密码
+      await pwdFormRef.value.validate((valid) => {
+        if (valid) {
+          commonApi.changePassword(pwdForm)
+              .then(() => {
+                ElMessage.success('密码修改成功')
+              })
+              .catch(() => {
+                ElMessage.error('操作失败')
+              })
+        }
+      })
+    }
   }
 </script>
 

+ 1 - 0
web/src/views/system/user/modules/user-dialog.vue

@@ -1,3 +1,4 @@
+
 <template>
   <ElDialog
     v-model="dialogVisible"