Browse Source

✨ feat: 新增用户管理功能

 - 增加企业微信绑定与解绑功能
 - 新增修改密码、换绑邮箱功能
Pchen. 7 months ago
parent
commit
756317d1e3

+ 25 - 2
src/app/lib/ServerAPI.js

@@ -72,9 +72,9 @@ class ServerAPI {
         )
     }
 
-    static UpdateInfo(uuid, session, code, callback = function () { }) {
+    static BindWXWork(uuid, session, code, callback = function () { }) {
         core.request(
-            "/User/UpdateInfo",
+            "/User/BindWXWork",
             {
                 uuid,
                 session,
@@ -85,6 +85,29 @@ class ServerAPI {
         )
     }
 
+    static BindEmail(uuid, session, email, code, callback = function () { }) {
+        core.request(
+            "/User/BindEmail",
+            {
+                uuid,
+                session,
+                email,
+                code
+            },
+            { method: 'POST' },
+            callback
+        )
+    }
+
+    static ChangePassword(uuid, session, oldpassword, password, callback = function () { }) {
+        core.request(
+            "/User/ChangePassword",
+            { uuid, session, oldpassword, password },
+            { method: 'POST' },
+            callback
+        )
+    }
+
     static getPermissions(uuid, session, callback = function () { }) {
         core.request(
             "/User/GetPermissions",

+ 2 - 2
src/pages/Mine/Mine.vue

@@ -60,11 +60,11 @@
                         <span>网站管理</span>
                     </div>
 
-                    <div class="button" @click="$router.push('/UpdateInfo')">
+                    <div class="button" @click="$router.push('/Setup')">
                         <el-icon :size="30">
                             <Setting />
                         </el-icon>
-                        <span>更新个人信息</span>
+                        <span>设置中心</span>
                     </div>
 
                     <div class="button" @click="logout()" v-if="app.user !== undefined">

+ 151 - 0
src/pages/Setup/ChangePassword/ChangePassword.vue

@@ -0,0 +1,151 @@
+<template>
+    <Header />
+
+    <div class="root">
+        <div class="content">
+            <h1>修改密码</h1>
+            <el-form :model="form" ref="formRef">
+                <el-form-item prop="oldpassword">
+                    <el-input v-model="form.oldpassword" placeholder="输入旧密码" type="password" :prefix-icon="Lock" />
+                </el-form-item>
+
+                <el-form-item prop="password">
+                    <el-input v-model="form.password" placeholder="输入新密码" type="password" :prefix-icon="Lock" />
+                </el-form-item>
+
+                <el-form-item prop="password1">
+                    <el-input v-model="form.password1" placeholder="再次输入新密码" type="password" :prefix-icon="Lock" />
+                </el-form-item>
+            </el-form>
+            <button @click="Submit()">提交</button>
+        </div>
+
+    </div>
+
+    <Footer />
+</template>
+
+<script setup>
+import Header from '../../../components/Header.vue';
+import Footer from '../../../components/Footer.vue';
+import { App } from '../../../app/app';
+import { useRouter } from 'vue-router';
+import { ServerAPI } from '../../../app/lib/ServerAPI';
+import { Lock } from '@element-plus/icons-vue';
+
+let router = useRouter();
+
+if (!App.hasUser()) {
+    ElMessage.error('请登录!')
+    router.replace({
+        name: "Login"
+    });
+}
+
+let formRef = ref();
+let form = reactive({
+    oldpassword: '',
+    password: '',
+    password1: ''
+});
+
+function CheckPassword(password) {
+    if (password.length < 8 || password.length > 16) {
+        return false;
+    }
+
+    // 使用正则表达式检查密码是否包含至少一个字母和一个数字
+    const hasLetter = /[a-zA-Z]/.test(password); // 检查是否包含字母
+    const hasNumber = /\d/.test(password);       // 检查是否包含数字
+
+    return hasLetter && hasNumber;
+}
+
+let Submit = () => {
+    if (form.oldpassword === '' ||form.password === '' || form.password1 === '')
+        return ElMessage.error('请确保每项不能为空');
+    if (!CheckPassword(form.password1))
+        return ElMessage.error('密码需在8到16位之间,且包含字母和数字');
+    if (form.password1 !== form.password)
+        return ElMessage.error('请确保两次输入的密码一致');
+
+    let oldpassword = btoa(form.oldpassword);
+    let password = btoa(form.password);
+
+    ServerAPI.ChangePassword(App.user.uuid, App.user.session, oldpassword, password, (res) => {
+        if (!res || res.code !== 0)
+            return ElMessage.error(`修改失败!${res.msg || ''}`);
+
+        ElMessage.success('修改成功!');
+    });
+}
+
+</script>
+
+<style scoped>
+.root {
+    min-height: 75vh;
+}
+
+.content {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    background-color: rgba(255, 255, 255, 0.7);
+    position: absolute;
+    top: 50%;
+    left: 50%;   transform: translate(-50%, -50%);
+    height: 350px;
+    width: 650px;
+    box-sizing: border-box;
+    border-radius: 10px;
+}
+
+.content .input {
+    display: flex;
+    flex-direction: column;
+    gap: 15px;
+}
+
+.content h1 {
+    color: #337ecc;
+    font-size: 1.6em;
+    margin: 0;
+    margin-bottom: 20px;
+}
+
+.el-input {
+    border-radius: 15px;
+    height: 50px;
+}
+
+.content button {
+    width: 100px;
+    height: 35px;
+    margin-top: 5px;
+    font-size: 1em;
+    border: none;
+    border-radius: 10px;
+    background-color: #337ecc;
+    color: #fff;
+}
+
+@media only screen and (max-width: 768px) {
+    .content {
+        width: 80%;
+        height: 350px;
+        padding: 25px;
+    }
+
+    .el-input {
+        height: 40px;
+    }
+
+    .content button {
+        height: 35px;
+        width: 150px;
+        font-size: 0.9em;
+    }
+}
+</style>

+ 145 - 0
src/pages/Setup/Setup.vue

@@ -0,0 +1,145 @@
+<template>
+    <Header />
+    <div class="mainRoot">
+        <div class="settings">
+            <router-link to="/UpdateInfo">
+                <div class="item">
+                    <div class="icon">
+                        <span class="iconfont">
+                            <el-icon>
+                                <Refresh />
+                            </el-icon>
+                        </span>
+                    </div>
+                    <div class="name">个人信息管理</div>
+                    <div class="btn">
+                        <span class="iconfont">
+                            <el-icon><arrow-right /></el-icon>
+                        </span>
+                    </div>
+                </div>
+            </router-link>
+
+            <router-link to="/ChangePassword">
+                <div class="item">
+                    <div class="icon">
+                        <span class="iconfont">
+                            <el-icon>
+                                <Lock />
+                            </el-icon>
+                        </span>
+                    </div>
+                    <div class="name">修改密码</div>
+                    <div class="btn">
+                        <span class="iconfont">
+                            <el-icon><arrow-right /></el-icon>
+                        </span>
+                    </div>
+                </div>
+            </router-link>
+
+            <div class="item end" @click="onDevelopment">
+                <div class="icon">
+                    <span class="iconfont">
+                        <el-icon>
+                            <QuestionFilled />
+                        </el-icon>
+                    </span>
+                </div>
+                <div class="name">帮助与反馈</div>
+                <div class="btn">
+                    <span class="iconfont">
+                        <el-icon><arrow-right /></el-icon>
+                    </span>
+                </div>
+            </div>
+
+        </div>
+
+    </div>
+</template>
+
+<script setup>
+import Header from '../../components/Header.vue'
+import { useRouter } from 'vue-router';
+import { App } from '../../app/app';
+import { QuestionFilled } from '@element-plus/icons-vue';
+
+let router = useRouter();
+
+if (!App.hasUser()) {
+    ElMessage({
+        message: "请登录",
+        type: 'warning',
+    });
+    router.replace({
+        name: "Login"
+    });
+}
+
+</script>
+
+<style scoped>
+.settings {
+    width: 70%;
+    margin: 0 auto;
+    padding: 12px;
+    background-color: #fff;
+    border-radius: 12px;
+    display: flex;
+    flex-direction: column;
+}
+
+.settings .item {
+    display: flex;
+    padding-top: 8px;
+    padding-bottom: 8px;
+    align-items: center;
+    border-bottom: 1px solid #eee;
+}
+
+.settings .item.end {
+    border-bottom: none;
+}
+
+.settings .item .icon {
+    width: 30px;
+    height: 30px;
+    /* border: 1px solid red; */
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.settings .item .icon .iconfont {
+    font-size: 20px;
+}
+
+.settings .item .name {
+    margin-left: 12px;
+    flex: 1;
+}
+
+.logoutBtn {
+    height: 45px;
+    background-color: #c8161d;
+    border-radius: 10px;
+    margin-left: 18px;
+    margin-right: 18px;
+    margin-bottom: 18px;
+    margin-top: 20px;
+    line-height: 45px;
+    text-align: center;
+    color: #fff;
+    font-weight: bold;
+    font-size: 16px;
+}
+
+@media only screen and (max-width: 768px) {
+    .settings {
+        margin-top: 10px;
+        width: 80%;
+    }
+
+}
+</style>

+ 204 - 0
src/pages/Setup/UserInfo/BindEmail.vue

@@ -0,0 +1,204 @@
+<template>
+    <Header />
+
+    <div class="root">
+        <div class="content">
+            <h1>更换绑定邮箱</h1>
+            <el-form :model="form" ref="formRef">
+                <el-form-item prop="email">
+                    <el-input v-model="form.email" placeholder="输入新邮箱" :prefix-icon="Postcard" />
+                </el-form-item>
+
+                <el-form-item prop="text">
+                    <div class="captcha">
+                        <el-input v-model="form.text" placeholder="输入验证码" class="captcha-input"
+                            :prefix-icon="Finished" />
+                        <img alt="验证码" :src="ImageCaptcha" @click="getCaptcha">
+                    </div>
+                </el-form-item>
+
+                <el-form-item prop="code">
+                    <el-input v-model="form.code" placeholder="邮箱验证码" :prefix-icon="Finished">
+                        <template #append>
+                            <div @click="sendEmail" v-if="time === 0">获取验证码</div>
+                            <div disabled v-else>{{ time }} s</div>
+                        </template>
+                    </el-input>
+                </el-form-item>
+            </el-form>
+            <button @click="Submit()">提交</button>
+        </div>
+
+    </div>
+
+    <Footer />
+</template>
+
+<script setup>
+import Header from '../../../components/Header.vue';
+import Footer from '../../../components/Footer.vue';
+import { App } from '../../../app/app';
+import { useRouter } from 'vue-router';
+import { ServerAPI } from '../../../app/lib/ServerAPI';
+import {  Postcard, Finished } from '@element-plus/icons-vue';
+
+let router = useRouter();
+
+if (!App.hasUser()) {
+    ElMessage.error('请登录!')
+    router.replace({
+        name: "Login"
+    });
+}
+
+let ImageCaptcha = ref('');
+let time = ref(0);
+let disabled = ref(false);
+let formRef = ref();
+let form = reactive({
+    email: App.user.email,
+    text: '',
+    code: ''
+});
+
+let getCaptcha = () => {
+    ServerAPI.ImageCaptcha((r) => {
+        if (!r || r.code != 0)
+            ElMessage.error(`获取图片验证码失败!${r.msg}`);
+
+        ImageCaptcha.value = r.data.img;
+        form.id = r.data.id;
+    })
+}
+
+onMounted(() => {
+    getCaptcha()
+})
+
+let settime = () => {
+    time.value = 60;
+    const timer = setInterval(() => {
+        if (time.value > 0) {
+            time.value--;
+        } else {
+            clearInterval(timer)
+        }
+    }, 1000)
+}
+
+let sendEmail = () => {
+    if (disabled.value)
+        return ElMessage.warning('您的操作太快了,休息一下吧!');
+    if (form.email == '')
+        return ElMessage.error('请填写邮箱');
+    if (form.text == '')
+        return ElMessage.error('请填写图片验证码');
+
+    disabled.value = true;
+    ServerAPI.SendEmail(form.email, form.text, form.id, 'bind', (r) => {
+        if (r && r.code === 0) {
+            ElMessage.success('发送邮箱验证码成功!');
+            settime();
+            disabled.value = false;
+        } else {
+            ElMessage.error(`获取邮箱验证码失败!${r.msg || ''}`);
+            disabled.value = false;
+        }
+    })
+}
+
+let Submit = () => {
+    if (form.email === '' || form.code === '' || form.text === '')
+        return ElMessage.error('请确保每项不能为空');
+
+    ServerAPI.BindEmail(App.user.uuid, App.user.session, form.email, form.code, (res) => {
+        if (!res || res.code !== 0){
+            getCaptcha();
+            return ElMessage.error(`绑定失败!${res.msg || ''}`);
+        }
+           
+        App.refershUser(res.data);
+        ElMessage.success('绑定成功!');
+        router.push('/UpdateInfo');
+    });
+}
+
+</script>
+
+<style scoped>
+.root {
+    min-height: 75vh;
+}
+
+.content {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+    background-color: rgba(255, 255, 255, 0.7);
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    height: 350px;
+    width: 650px;
+    box-sizing: border-box;
+    border-radius: 10px;
+}
+
+.content .input {
+    display: flex;
+    flex-direction: column;
+    gap: 15px;
+}
+
+.content h1 {
+    color: #337ecc;
+    font-size: 1.6em;
+    margin: 0;
+    margin-bottom: 20px;
+}
+
+.el-input {
+    border-radius: 15px;
+    height: 50px;
+}
+
+.content .captcha {
+    display: flex;
+    height: 50px;
+}
+
+.content button {
+    width: 180px;
+    height: 40px;
+    margin-top: 10px;
+    font-size: 1em;
+    border: none;
+    border-radius: 10px;
+    background-color: #337ecc;
+    color: #fff;
+}
+
+@media only screen and (max-width: 768px) {
+    .content {
+        width: 80%;
+        height: 350px;
+        padding: 25px;
+    }
+
+    .el-input {
+        height: 40px;
+    }
+
+    .content .captcha {
+        height: 40px;
+    }
+
+    .content button {
+        height: 35px;
+        width: 150px;
+        font-size: 0.9em;
+    }
+}
+</style>

+ 11 - 15
src/pages/Setup/UpdateInfoStage2.vue → src/pages/Setup/UserInfo/BindWXWork.vue

@@ -1,7 +1,7 @@
 <script setup>
 import { useRouter } from 'vue-router';
-import { App } from '../../app/app';
-import { ServerAPI } from '../../app/lib/ServerAPI';
+import { App } from '../../../app/app';
+import { ServerAPI } from '../../../app/lib/ServerAPI';
 
 let router = useRouter();
 
@@ -19,41 +19,37 @@ onMounted(async () => {
 
     if (num === 0 && code === '' || code === null) {
         num = 1
-        ServerAPI.WXWorkUrl(num, 'update', (res) => {
+        ServerAPI.WXWorkUrl(num, 'bind', (res) => {
             loading.close();
 
-            if (res == undefined || res.code != 0) 
-                return ElMessage.error(`更新失败!${res.msg}`)
+            if (res == undefined || res.code != 0)
+                return ElMessage.error(`更新失败!${res.msg || ''}`)
 
             window.location.href = res.data.url
         })
     }
 
     try {
-        ServerAPI.UpdateInfo(App.user.uuid, App.user.session, code,(res) => {
+        ServerAPI.BindWXWork(App.user.uuid, App.user.session, code, (res) => {
             if (res.code !== 0) {
                 loading.close();
-                ElMessage.error(`更新失败!${res.msg}`);
+                ElMessage.error(`绑定失败!${res.msg || ''}`);
                 router.replace({
-                    name: "UpdateInfoStage1",
+                    name: "UpdateInfo",
                 })
                 return;
             }
 
             loading.close();
-            ElMessage.success("更新个人信息成功");
+            ElMessage.success("绑定企业微信成功");
             App.refershUser(res.data);
-
         })
     } catch (error) {
         loading.close();
-        ElMessage({
-            message: "更新失败!请稍后再试" + error,
-            type: "error"
-        });
+        ElMessage.error(`绑定失败!${error.message || ''}`)
 
         router.replace({
-            name: "UpdateInfoStage1",
+            name: "UpdateInfo",
         })
     }
 });

+ 18 - 30
src/pages/Setup/UpdateInfoStage1.vue → src/pages/Setup/UserInfo/UpdateInfoStage1.vue

@@ -9,19 +9,21 @@
                     <el-input v-model="form.username" disabled />
                 </el-form-item>
 
+                <el-form-item label="邮箱" prop="email">
+                    <el-input v-model="form.email" disabled />
+                </el-form-item>
+
                 <el-form-item label="企业微信ID" prop="wxid">
                     <el-input v-model="form.wxid" disabled />
                 </el-form-item>
             </el-form>
 
-            <div class="btn" @click="onGetUrl">
-                获取企业微信资料
+            <div class="btn" @click="BindWXWork">
+                绑定企业微信
             </div>
 
-            <div class="agreementBox">
-                <div class="text">
-                    我们将获取你的用户名、用户ID等信息
-                </div>
+            <div class="btn" @click="$router.push('/UpdateInfo/BindEmail')">
+                更换绑定邮箱
             </div>
         </div>
 
@@ -31,11 +33,11 @@
 </template>
 
 <script setup>
-import Header from '../../components/Header.vue';
-import Footer from '../../components/Footer.vue';
-import { App } from '../../app/app';
+import Header from '../../../components/Header.vue';
+import Footer from '../../../components/Footer.vue';
+import { App } from '../../../app/app';
 import { useRouter } from 'vue-router';
-import { ServerAPI } from '../../app/lib/ServerAPI';
+import { ServerAPI } from '../../../app/lib/ServerAPI';
 
 let router = useRouter();
 
@@ -49,15 +51,14 @@ if (!App.hasUser()) {
 let formRef = ref();
 let form = reactive({
     username: App.user.username,
-    wxid: App.user.wxid
+    wxid: App.user.wxid || '未绑定企业微信',
+    email: App.user.email
 });
 
-
-let onGetUrl = function () {
-    ServerAPI.WXWorkUrl(0, 'update', (res) => {
+let BindWXWork = function () {
+    ServerAPI.WXWorkUrl(0, 'bind', (res) => {
         if (res == undefined || res.code != 0) 
-            return ElMessage.error(`获取登录链接失败!${res.msg}`);
-        
+            return ElMessage.error(`获取登录链接失败!${res.msg || ''}`);
         window.location.href = res.data.url
     });
 }
@@ -65,7 +66,7 @@ let onGetUrl = function () {
 
 <style scoped>
 .root {
-    min-height: 70vh;
+    min-height: 75vh;
 }
 
 .avatar {
@@ -96,19 +97,6 @@ let onGetUrl = function () {
     font-size: 14px;
 }
 
-.agreementBox .text {
-    margin-left: 5px;
-    display: inline;
-    font-size: 12px;
-    color: #777;
-    line-height: 20px;
-}
-
-.agreementBox .text .bold {
-    font-weight: bold;
-    color: #000;
-}
-
 @media only screen and (max-width: 768px) {
     .content {
         width: 90%

+ 24 - 7
src/router/index.js

@@ -50,21 +50,38 @@ const routes = [
         component: () => import('../pages/Admin/ClockIn/components/EditItem.vue'),
         meta: { title: '编辑考勤项目' }
     },
-    
+    {
+        path: "/Setup",
+        name: "Setup",
+        component: () => import('../pages/Setup/Setup.vue'),
+        meta: { title: '设置中心' }
+    },
     {
         name: "UpdateInfo",
         path: "/UpdateInfo",
-        component: () => import('../pages/Setup/UpdateInfoStage1.vue'),
+        component: () => import('../pages/Setup/UserInfo/UpdateInfoStage1.vue'),
         meta: { title: '更新个人信息' },
         children: [
             {
-                name: "UpdateInfoStage2",
-                path: "Stage2",
-                component: () => import('../pages/Setup/UpdateInfoStage2.vue'),
-                meta: { title: '更新个人信息' }
+                name: "BindWXWork",
+                path: "BindWXWork",
+                component: () => import('../pages/Setup/UserInfo/BindWXWork.vue'),
+                meta: { title: '绑定企业微信' }
             }
         ]
     },
+    {
+        name: "BindEmail",
+        path: "/UpdateInfo/BindEmail",
+        component: () => import('../pages/Setup/UserInfo/BindEmail.vue'),
+        meta: { title: '更换绑定邮箱' }
+    },
+    {
+        name: "ChangePassword",
+        path: "/ChangePassword",
+        component: () => import('../pages/Setup/ChangePassword/ChangePassword.vue'),
+        meta: { title: '修改密码' }
+    },
     {
         path: "/Admin",
         name: "Admin",
@@ -80,7 +97,7 @@ const router = VueRouter.createRouter({
 });
 
 router.beforeEach((to, from, next) => {
-    if (!to.meta.title) 
+    if (!to.meta.title)
         document.title = 'Double_X 考勤'
     else
         document.title = to.meta.title