sawAdmin/src/views/pages/ucenter.vue

646 lines
21 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<div class="user-container">
<el-card class="user-profile" shadow="hover" :body-style="{ padding: '0px' }">
<div class="user-profile-bg"></div>
<div class="user-avatar-wrap">
<el-avatar class="user-avatar" :size="120" :src="avatarImg" />
</div>
<div class="user-info">
<div class="info-name">{{ name }}</div>
</div>
<div class="user-footer">
<div class="user-footer-item">
<el-statistic title="提问数" :value="userStatistic.question" />
</div>
<div class="user-footer-item">
<el-statistic title="会话数" :value="userStatistic.session" />
</div>
<div class="user-footer-item">
<el-statistic title="文件数" :value="userStatistic.file_count" />
</div>
<div class="user-footer-item">
<el-statistic title="总计" :value="userStatistic.total" />
</div>
</div>
</el-card>
<el-card
class="user-content"
shadow="hover"
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }"
>
<el-tabs tab-position="left" v-model="activeName">
<!-- <el-tab-pane name="label1" label="消息通知" class="user-tabpane">
<TabsComp />
</el-tab-pane> -->
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
<div class="crop-wrap" v-if="activeName === 'label2'">
<vueCropper
ref="cropper"
:img="imgSrc"
:autoCrop="true"
:centerBox="true"
:full="true"
mode="contain"
>
</vueCropper>
</div>
<el-button class="crop-demo-btn" type="primary"
>选择图片
<input class="crop-input" type="file" name="image" accept="image/*" @change="setImage" />
</el-button>
<el-button type="success" @click="saveAvatar">上传并保存</el-button>
</el-tab-pane>
<el-tab-pane name="label3" label="修改密码" class="user-tabpane">
<el-form class="w500" label-position="top">
<el-form-item label="旧密码:">
<el-input type="password" v-model="form.old"></el-input>
</el-form-item>
<el-form-item label="新密码:">
<el-input type="password" v-model="form.new"></el-input>
</el-form-item>
<el-form-item label="确认新密码:">
<el-input type="password" v-model="form.new1"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="resetPassword ">保存</el-button>
</el-form-item>
<el-link type="primary" @click="reset_password()">忘记密码?使用验证码重置</el-link>
</el-form>
</el-tab-pane>
<el-tab-pane name="label4" v-if="isUserInfoLoaded" label="详细信息" class="user-tabpane">
<TableEdit :form-data="userInfo" :options="options_edit" :edit="true" :update="updateUserInfo" />
</el-tab-pane>
<el-tab-pane name="label5" label="第三方账号" class="user-tabpane">
<div>
<el-select v-model="activePlatformName" placeholder="请选择第三方平台" size="small" style="width: 200px;">
<!-- <el-option label="QQ" value="qq"></el-option>
<el-option label="Github" value="github"></el-option>
<el-option label="gitee" value="gitee"></el-option>
<el-option label="google" value="google"></el-option> -->
<!-- thirdPartyPlatform -->
<template v-for="(item, index) in thirdPartyPlatform" :key="index">
<el-option :label="item.label" :value="item.value"></el-option>
</template>
</el-select>
<el-button type="primary" @click="thirdLogin(activePlatformName)">绑定</el-button>
</div>
<div>
<p>已绑定的第三方登录账号</p>
<template v-for="(item, index) in thirdPartyUserInfo" :key="index">
<el-row :gutter="20" class="user-tabpane">
<el-col :span="12">
<el-tag>{{ item.third_party_platform }}</el-tag>
</el-col>
<el-col :span="12">
<el-avatar :src="item.third_party_user_avatar" size="small"></el-avatar>
</el-col>
<el-col :span="12">
<el-tag>{{ item.third_party_user_name }}</el-tag>
</el-col>
<el-col :span="12">
<el-button type="danger" @click="unBindThirdParty(item.ID)">解绑</el-button>
</el-col>
<!-- <el-tag>{{ item.third_party_platform }}</el-tag>
<el-avatar :src="item.third_party_user_avatar" size="small"></el-avatar>
<el-tag>{{ item.third_party_user_name }}</el-tag>
<el-button type="danger" @click="unBindThirdParty(item.ID)">解绑</el-button> -->
</el-row>
</template>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script setup lang="ts" name="ucenter">
import { reactive, ref,inject } from 'vue';
import { VueCropper } from 'vue-cropper';
import 'vue-cropper/dist/index.css';
import avatar from '@/assets/img/img.jpg';
import TabsComp from '../element/tabs.vue';
import {GetUserInfoService} from "@/api/user";
import { GetUserStatisticService } from "@/api/user";
import { UploadFileService } from "@/api/tool";
import { UserInfo } from '@/types/user';
import { ThirdPartyUserInfo } from '@/types/user';
import { FormOption, FormOptionList } from '@/types/form-option';
import { avatarEmits, ElMessage } from 'element-plus';
import TableEdit from '@/components/table-edit.vue';
import {genResetPassword} from "@/api/user";
import {updateUserInfoService} from "@/api/user";
import { useRouter } from "vue-router";
import {getThirdPartyUUID,getThirdPartyLoginStatus,getThirdPartyLoginUrl, getThirdPartyInfo, deleteThirdPartyInfo} from "@/api/user";
const name = localStorage.getItem('ms_username');
const qqButtonBgImage = ref('https://wiki.connect.qq.com/wp-content/uploads/2016/12/Connect_logo_4.png');
const form = reactive({
new1: '',
new: '',
old: '',
});
const userStatistic = reactive({
question: 0,
session: 0,
total: 0,
file_count: 0,
});
const userInfo = ref<UserInfo>();
const isUserInfoLoaded = ref(false);
const globalData = inject("globalData");
const activeName = ref('label2');
const activePlatformName = ref('github');
const router = useRouter();
const avatarImg = ref('');
const imgSrc = ref('');
const cropImg = ref('');
const cropper: any = ref();
const maxLoginRepeatRequest = 60; //最大请求次数
const currentLoginRequest = ref(0); //当前请求次数
const thirdPartyUserInfo = ref<ThirdPartyUserInfo[]>([]);
const thirdPartyPlatform = ref([
{ label: 'QQ', value: 'qq' },
{ label: 'Github', value: 'github' },
{ label: 'Gitee', value: 'gitee' },
{ label: 'Google', value: 'google' },
{ label: 'Facebook', value: 'facebook'},
{ label: 'StackOverflow', value: 'stackoverflow' },
]);
const reset_password = () => {
localStorage.removeItem("ms_username");
router.push('/reset-pwd');
};
const setImage = (e: any) => {
const file = e.target.files[0];
if (!file.type.includes('image/')) {
ElMessage.error('请选择图片文件');
return;
}
// 可选:检查文件大小
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
console.error('文件大小超过限制');
return;
}
const reader = new FileReader();
reader.onload = (event: any) => {
imgSrc.value = event.target.result;
cropper.value && cropper.value.replace(event.target.result);
};
reader.readAsDataURL(file);
};
const updateUserInfo = async (data: any) => {
let result = null;
try{
let req={
token:localStorage.getItem("token"),
id:data.ID,
name:data.Name,
age:data.Age,
gender:data.Gender,
password:data.Password,
email:data.Email,
avatar:data.Avatar,
role:data.Role
};
result = await updateUserInfoService(req)
if (result["code"] === 0) {
ElMessage.success("更新成功");
} else {
ElMessage.error("更新失败");
}
}catch(e){
console.log(e);
}
};
const resetPassword = async () =>{
let req={
old_password: form.old,
new_password: form.new1,
email: userInfo.value.Email,
type:1
}
try{
let result = await genResetPassword(req);
if (result["code"] === 0) {
//重置成功返回新token
if (result.data.token) {
localStorage.setItem('token', result.data.token);
globalData["token"] = result.data.token;
ElMessage.success('重置密码成功');
}
} else {
ElMessage.error(result["msg"]);
}
}catch(e){
console.log(e)
}
};
//编辑弹窗
let options_edit = ref<FormOption>({
labelWidth: '100px',
span: 12,
list: [
// {prop: 'Avatar',label: '头像', type: 'input', required: false},
{ type: 'input', label: '用户ID', prop: 'ID', required: true,disabled:true },
{ type: 'input', label: '用户名', prop: 'Name', required: true },
{ type: 'input', label: '年龄', prop: 'Age', required: false },
{ type: 'input', label: '密码', prop: 'Password', required: false,disabled:true },
{ type: 'input', label: '邮箱', prop: 'Email', required: true,disabled:true },
{ type: 'input', label: '性别', prop: 'Gender', required: false },
//select 选择框,可选择admin与user两种角色
{ type: 'select', label: '角色', prop: 'Role', opts: [{label:"管理员",value:"admin"},{label:"普通用户",value:"user"}], required: false,disabled:true },
{ type: 'input', label: '注册时间', prop: 'CreatedAt', required: false,disabled:true },
{ type: 'input', label: '上次修改时间', prop: 'UpdatedAt', required: false,disabled:true },
]
})
const GetMyUserInfo = async () => {
let req = {
token: localStorage.getItem('token'),
id: localStorage.getItem('userId')
};
try{
let result = await GetUserInfoService(req);
if (result["code"] == 0) {
avatarImg.value = result.data.Avatar == '' ? avatar : result.data.Avatar;
imgSrc.value = avatarImg.value;
userInfo.value = result.data;
userInfo.value.Password = '**********';
}else{
ElMessage.error(result["msg"]);
}
isUserInfoLoaded.value = true;
}catch(e){
console.log(e);
}
};
GetMyUserInfo();
const cropImage = () => {
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
};
// Data URL 转 File 对象的函数
const dataURLtoFile = (dataurl, filename) => {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)?.[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
};
const getUserStatistics = async () => {
let req = {
token: localStorage.getItem('token'),
id: localStorage.getItem('userId')
};
try{
let result = await GetUserStatisticService(req);
if (result["code"] == 0) {
userStatistic.question = result.data.message_count;
userStatistic.session = result.data.session_count;
userStatistic.file_count = result.data.file_count;
userStatistic.total = userStatistic.question + userStatistic.session + userStatistic.file_count;
}else{
ElMessage.error(result["msg"]);
}
}catch(e){
console.log(e);
}
};
getUserStatistics();
const saveAvatar =async () => {
let token = localStorage.getItem('token');
avatarImg.value = cropImg.value;
let formData = new FormData();
//文件
let file= dataURLtoFile(imgSrc.value, 'avatar.jpg');
//文件类型限制
const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg'];
if (!allowedTypes.includes(file.type)) {
ElMessage.error('文件类型不符合要求请选择jpg或png格式的图片');
return;
}
//文件大小限制
const maxSize = 2 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
ElMessage.error('文件大小超过限制最大2MB');
return;
}
formData.append('file', file);
//console.log("add file: " + this.file);
formData.append('upload_type', "1");
formData.append('md5', "");
formData.append('auth_type', "public");
//console.log("formData:",formData);
let result = await UploadFileService(formData, token);
if (result["code"] !== 0) {
ElMessage.error('上传文件失败,请稍后再试');
return;
}
let resp_data = result.data;
//console.log("resp:",resp_data);
let url = "https://pm.ljsea.top/tool/file/"+resp_data.FileStoreName;
userInfo.value.Avatar = url;
avatarImg.value = url;
//更新用户信息
await updateUserInfo(userInfo.value);
};
const thirdLogin = async (type) => {
//如果该类型的登录已经绑定,则不允许再次绑定
for (let i = 0; i < thirdPartyUserInfo.value.length; i++) {
if (thirdPartyUserInfo.value[i].third_party_platform === type) {
ElMessage.error("该平台已经绑定,请勿重复绑定!若要绑定其它账号,请先解绑!");
return;
}
}
//获取uuid
let uuidResp = await getThirdPartyUUID({"type": type});
if (uuidResp["code"] !== 0) {
ElMessage.error("获取UUID失败请稍后再试");
return;
}
let uuid = uuidResp["data"];
if (!uuid) {
ElMessage.error("获取UUID失败请稍后再试");
return;
}
let result={};
//判断登录平台是否支持,thirdPartyPlatform
for(let i = 0; i < thirdPartyPlatform.value.length; i++) {
if (thirdPartyPlatform.value[i].value === type) {
break;
}
if (i === thirdPartyPlatform.value.length - 1) {
ElMessage.error("不支持的登录平台!请稍后再试:"+type);
return;
}
}
result = await getThirdPartyLoginUrl({uuid: uuid,"type": "add", "platform": type, "token": localStorage.getItem("token") });
if (result["code"] !== 0) {
ElMessage.error("获取登录地址失败!请稍后再试");
return;
}
let loginUrl = result["data"];
if (!loginUrl) {
ElMessage.error("获取登录地址失败!请稍后再试");
return;
}
//获取登录状态每2秒请求一次
let timer = setInterval(async () => {
currentLoginRequest.value++;
if (currentLoginRequest.value > maxLoginRepeatRequest) {
clearInterval(timer);
ElMessage.error("登录超时,请重新登录!");
return;
}
let statusResp = await getThirdPartyLoginStatus({uuid: uuid});
if (statusResp["code"] !== 0) {
ElMessage.error("获取登录状态失败!请稍后再试");
clearInterval(timer);
return;
}
if(statusResp["code"] === 9){
ElMessage.error("服务器错误,请稍后再试!");
clearInterval(timer);
return;
}
if(statusResp["data"]["status"] === 163) {
ElMessage.error("该账号未绑定,请先绑定账号!");
clearInterval(timer);
return;
}
let status = statusResp["data"];
if(status["status"] === 0) {
//登录成功
clearInterval(timer);
let userInfo = status["user_info"];
let token = userInfo["token"];
if (!token) {
ElMessage.error("获取登录状态失败!请稍后再试");
return;
}
globalData["token"] = token;
localStorage.setItem("token", token);
localStorage.setItem("userId", userInfo["id"]);
localStorage.setItem("username", status["username"]);
let now = new Date();
localStorage.setItem("end_time", (now.setDate(now.getHours() + 12)).toString()); //过期时间
getThirdPartyUserInfo();
return;
}
}, 2000);
//新标签页打开登录地址
// window.open(loginUrl, "_blank");
//新的浏览器窗口打开标签页
const width = 800;
const height = 600;
// 计算窗口在屏幕中心的位置
const left = (window.screen.width - width) / 2;
const top = (window.screen.height - height) / 2;
const features = `width=${width},height=${height},left=${left},top=${top}`;
// 创建新窗口
const newWindow = window.open(loginUrl, '_blank', features);
};
//获取绑定的第三方账号信息
const getThirdPartyUserInfo = async () => {
let req = {
token: localStorage.getItem('token'),
};
try{
let result = await getThirdPartyInfo(req);
if (result["code"] == 0) {
//绑定的第三方账号信息
console.log(result.data);
thirdPartyUserInfo.value = result.data;
}else{
ElMessage.error(result["msg"]);
}
}catch(e){
console.log(e);
}
};
getThirdPartyUserInfo();
const unBindThirdParty = async (id) => {
let req = {
token: localStorage.getItem('token'),
id: id
};
try{
let result = await deleteThirdPartyInfo(req);
if (result["code"] == 0) {
//解绑成功
ElMessage.success("解绑成功");
getThirdPartyUserInfo();
}else{
ElMessage.error(result["msg"]);
}
}catch(e){
console.log(e);
}
};
</script>
<style scoped>
.user-container {
display: flex;
}
.user-profile {
position: relative;
}
.user-profile-bg {
width: 100%;
height: 200px;
background-image: url('../../assets/img/ucenter-bg.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.user-profile {
width: 500px;
margin-right: 20px;
flex: 0 0 auto;
align-self: flex-start;
}
.user-avatar-wrap {
position: absolute;
top: 135px;
width: 100%;
text-align: center;
}
.user-avatar {
border: 5px solid #fff;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 7px 12px 0 rgba(62, 57, 107, 0.16);
}
.user-info {
text-align: center;
padding: 80px 0 30px;
}
.info-name {
margin: 0 0 20px;
font-size: 22px;
font-weight: 500;
color: #373a3c;
}
.info-desc {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 5px;
}
.info-desc,
.info-desc a {
font-size: 18px;
color: #55595c;
}
.info-icon {
margin-top: 10px;
}
.info-icon i {
font-size: 30px;
margin: 0 10px;
color: #343434;
}
.user-content {
flex: 1;
}
.user-tabpane {
padding: 10px 20px;
}
.crop-wrap {
width: 600px;
height: 350px;
margin-bottom: 20px;
}
.crop-demo-btn {
position: relative;
}
.crop-input {
position: absolute;
width: 100px;
height: 40px;
left: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
.w500 {
width: 500px;
}
.user-footer {
display: flex;
border-top: 1px solid rgba(83, 70, 134, 0.1);
}
.user-footer-item {
padding: 20px 0;
width: 33.3333333333%;
text-align: center;
}
.user-footer > div + div {
border-left: 1px solid rgba(83, 70, 134, 0.1);
}
.login-btn {
display: block;
width: 100%;
}
</style>
<style>
.el-tabs.el-tabs--left {
height: 100%;
}
</style>