用户管理完成,用户中心完成

This commit is contained in:
junleea 2025-03-19 20:01:03 +08:00
parent 04955c9d4d
commit 996d1a2cf8
11 changed files with 796 additions and 447 deletions

136
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"cropperjs": "^2.0.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
@ -22,6 +23,7 @@
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-cropperjs": "^5.0.0",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",
@ -69,6 +71,115 @@
"node": ">=6.9.0"
}
},
"node_modules/@cropper/element": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element/-/element-2.0.0.tgz",
"integrity": "sha512-lsthn0nQq73GExUE7Mg/ss6Q3RXADGDv055hxoLFwvl/wGHgy6ZkYlfLZ/VmgBHC6jDK5IgPBFnqrPqlXWSGBA==",
"dependencies": {
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-canvas": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.0.0.tgz",
"integrity": "sha512-GPtGJgSm92crJhhhwUsaMw3rz2KfJWWSz7kRAlufFEV/EHTP5+6r6/Z1BCGRna830i+Avqbm435XLOtA7PVJwA==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-crosshair": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.0.0.tgz",
"integrity": "sha512-KfPfyrdeFvUC31Ws7ATtcalWWSaMtrC6bMoCipZhqbUOE7wZoL4ecDSL6BUOZxPa74awZUqfzirCDjHvheBfyw==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-grid": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.0.0.tgz",
"integrity": "sha512-i78SQ0IJTLFveKX6P7svkfMYVdgHrQ8ZmmEw8keFy9n1ZVbK+SK0UHK5FNMRNI/gtVhKJOGEnK/zeyjUdj4Iyw==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-handle": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.0.0.tgz",
"integrity": "sha512-ZJvW+0MkK9E8xYymGdoruaQn2kwjSHFpNSWinjyq6csuVQiCPxlX5ovAEDldmZ9MWePPtWEi3vLKQOo2Yb0T8g==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-image": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.0.0.tgz",
"integrity": "sha512-9BxiTS/aHRmrjopaFQb9mQQXmx4ruhYHGkDZMVz24AXpMFjUY6OpqrWse/WjzD9tfhMFvEdu17b3VAekcAgpeg==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/element-canvas": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-selection": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.0.0.tgz",
"integrity": "sha512-ensNnbIfJsJ8bhbJTH/RXtk2URFvTOO4TvfRk461n2FPEC588D7rwBmUJxQg74IiTi4y1JbCI+6j+4LyzYBLCQ==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/element-canvas": "^2.0.0",
"@cropper/element-image": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-shade": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.0.0.tgz",
"integrity": "sha512-jv/2bbNZnhU4W+T4G0c8ADocLIZvQFTXgCf2RFDNhI5UVxurzWBnDdb8Mx8LnVplnkTqO+xUmHZYve0CwgWo+Q==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/element-canvas": "^2.0.0",
"@cropper/element-selection": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/element-viewer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.0.0.tgz",
"integrity": "sha512-zY+3VRN5TvpM8twlphYtXw0tzJL2VgzeK7ufhL1BixVqOdRxwP13TprYIhqwGt9EW/SyJZUiaIu396T89kRX8A==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/element-canvas": "^2.0.0",
"@cropper/element-image": "^2.0.0",
"@cropper/element-selection": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/@cropper/elements": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/elements/-/elements-2.0.0.tgz",
"integrity": "sha512-PQkPo1nUjxLFUQuHYu+6atfHxpX9B41Xribao6wpvmvmNIFML6LQdNqqWYb6LyM7ujsu71CZdBiMT5oetjJVoQ==",
"dependencies": {
"@cropper/element": "^2.0.0",
"@cropper/element-canvas": "^2.0.0",
"@cropper/element-crosshair": "^2.0.0",
"@cropper/element-grid": "^2.0.0",
"@cropper/element-handle": "^2.0.0",
"@cropper/element-image": "^2.0.0",
"@cropper/element-selection": "^2.0.0",
"@cropper/element-shade": "^2.0.0",
"@cropper/element-viewer": "^2.0.0"
}
},
"node_modules/@cropper/utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@cropper/utils/-/utils-2.0.0.tgz",
"integrity": "sha512-cprLYr+7kK3faGgoOsTW9gIn5sefDr2KwOmgyjzIXk+8PLpW8FgFKEg5FoWfRD5zMAmkCBuX6rGKDK3VdUEGrg=="
},
"node_modules/@ctrl/tinycolor": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
@ -847,6 +958,15 @@
"node": ">=0.8"
}
},
"node_modules/cropperjs": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-2.0.0.tgz",
"integrity": "sha512-TO2j0Qre01kPHbow4FuTrbdEB4jTmGRySxW49jyEIqlJZuEBfrvCTT0vC3eRB2WBXudDfKi1Onako6DKWKxeAQ==",
"dependencies": {
"@cropper/elements": "^2.0.0",
"@cropper/utils": "^2.0.0"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@ -2474,6 +2594,22 @@
"resolved": "https://registry.npmjs.org/vue-cropper/-/vue-cropper-1.1.1.tgz",
"integrity": "sha512-WsqKMpaBf9Osi1LQlE/5AKdD0nHWOy1asLXocaG8NomOWO07jiZi968+/PbMmnD0QbPJOumDQaGuGa13qys85A=="
},
"node_modules/vue-cropperjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-5.0.0.tgz",
"integrity": "sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==",
"dependencies": {
"cropperjs": "^1.5.6"
},
"peerDependencies": {
"vue": ">=3.0.0"
}
},
"node_modules/vue-cropperjs/node_modules/cropperjs": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz",
"integrity": "sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA=="
},
"node_modules/vue-echarts": {
"version": "6.6.9",
"resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-6.6.9.tgz",

View File

@ -13,6 +13,7 @@
"@wangeditor/editor-for-vue": "^5.1.12",
"axios": "^1.6.3",
"countup.js": "^2.8.0",
"cropperjs": "^2.0.0",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.6.3",
@ -22,6 +23,7 @@
"pinia": "^2.1.7",
"vue": "^3.4.5",
"vue-cropper": "1.1.1",
"vue-cropperjs": "^5.0.0",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-schart": "^2.0.0",

48
src/api/tool.ts Normal file
View File

@ -0,0 +1,48 @@
import request from '@/utils/request2'
export const GetRedisInfoService = (Data) => {
const params = new URLSearchParams();
for (let key in Data) {
params.append(key, Data[key])
}
let request1 = getRequest();
return request1.post('/tool/get_redis', params,{
headers: {
'token': Data.token, // 闂佽绻愭蹇涘箯閿燂拷 token 闂備礁鎼ú锔锯偓绗涘啰鏆﹂柛娆忣槺閳绘棃鏌i幋鐏活亝绂嶉崼鏇熺厽闁靛ǹ鍎遍褔鏌熼煬鎻掆偓婵嬪箖瑜忔禒锔炬喆閿濆懍澹曢梺璺ㄥ櫐閹凤拷
}
})
}
function getRequest() {
let server = localStorage.getItem('cid_server');
let request1 = null;
if (server === "tx.ljsea.top") {
request1 = request;
}else{
request1 = request;
}
return request1;
}
export const UploadFileService = (formData,token) => {
//let request1 = getRequest();
return request.post('/tool/upload', formData,{
headers: {
'Content-Type': 'multipart/form-data',
'token': token,
}
})
}
export const GetFileInfoByMd5Service = (Data) => {
const params = new URLSearchParams();
for (let key in Data) {
params.append(key, Data[key])
}
//let request1 = getRequest();
return request.post('/tool/file_list', params,{
headers: {
'token': Data.token,
}
})
}

View File

@ -45,9 +45,6 @@
</span>
<template #dropdown>
<el-dropdown-menu>
<a href="https://github.com/lin-xin/vue-manage-system" target="_blank">
<el-dropdown-item>项目仓库</el-dropdown-item>
</a>
<el-dropdown-item command="user">个人中心</el-dropdown-item>
<el-dropdown-item divided command="loginout">退出登录</el-dropdown-item>
</el-dropdown-menu>

View File

@ -13,4 +13,40 @@ export interface Register {
username: string;
password: string;
email: string;
}
/**
*
* "ID": 1,
"CreatedAt": "2024-08-30T14:35:15.556734+08:00",
"UpdatedAt": "2025-03-11T15:13:27.972681+08:00",
"DeletedAt": null,
"Name": "lijun",
"Age": 24,
"Email": "lijun@ljsea.top",
"Password": "",
"Gender": "男",
"Role": "admin",
"Redis": true,
"Run": true,
"Upload": true,
"VideoFunc": true,
"DeviceFunc": true,
"CIDFunc": true,
"Avatar": "https://gep.ljsea.top/tool/file/d5a17d88-1641-4666-a9e1-53fbb04a6abf.jpg",
"CreateTime": "",
"UpdateTime": ""
*/
export interface UserInfo {
ID: number;
CreatedAt: string;
UpdatedAt: string;
DeletedAt: string;
Name: string;
Password: string;
Age: number;
Email: string;
Gender: string;
Role: string;
Avatar: string;
}

View File

@ -1,58 +1,71 @@
<template>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">大学生学业作品AI生成工具</div>
</div>
<el-form :model="param" :rules="rules" ref="login" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名或邮箱">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox class="pwd-checkbox" v-model="checked" label="记住密码" />
<el-link type="primary" @click="$router.push('/reset-pwd')">忘记密码</el-link>
</div>
<el-button class="login-btn" type="primary" size="large" @click="onLogin">登录</el-button>
<p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')">立即注册</el-link>
</p>
</el-form>
<div class="login-bg">
<div class="login-container">
<div class="login-header">
<img class="logo mr10" src="../../assets/img/logo.svg" alt="" />
<div class="login-title">大学生学业作品AI生成工具</div>
</div>
<el-form :model="param" :rules="rules" ref="login" size="large">
<el-form-item prop="username">
<el-input v-model="param.username" placeholder="用户名或邮箱">
<template #prepend>
<el-icon>
<User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
placeholder="密码"
v-model="param.password"
@keyup.enter="submitForm(login)"
>
<template #prepend>
<el-icon>
<Lock />
</el-icon>
</template>
</el-input>
</el-form-item>
<div class="pwd-tips">
<el-checkbox
class="pwd-checkbox"
v-model="checked"
label="记住密码"
/>
<el-link type="primary" @click="$router.push('/reset-pwd')"
>忘记密码</el-link
>
</div>
<el-button
class="login-btn"
type="primary"
size="large"
@click="onLogin"
>登录</el-button
>
<p class="login-text">
没有账号<el-link type="primary" @click="$router.push('/register')"
>立即注册</el-link
>
</p>
</el-form>
</div>
</div>
</template>
<script setup>
import { ref, reactive,inject } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import {loginService} from "@/api/user";
import {GetUserInfoService} from "@/api/user";
import { usePermissStore } from '@/store/permiss';
import { ref, reactive, inject } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { loginService } from "@/api/user";
import { GetUserInfoService } from "@/api/user";
import { usePermissStore } from "@/store/permiss";
//
const lgStr = localStorage.getItem('login-param');
const lgStr = localStorage.getItem("login-param");
const defParam = lgStr ? JSON.parse(lgStr) : null;
const globalData = inject("globalData");
const permiss = usePermissStore();
@ -62,26 +75,26 @@ const checked = ref(lgStr ? true : false);
const router = useRouter();
//
const param = reactive({
username: defParam ? defParam.username : '',
password: defParam ? defParam.password : ''
username: defParam ? defParam.username : "",
password: defParam ? defParam.password : "",
});
//
const rules = {
username: [
{
required: true,
message: '请输入用户名',
trigger: 'blur'
}
],
password: [
{
required: true,
message: '请输入密码',
trigger: 'blur'
}
]
username: [
{
required: true,
message: "请输入用户名",
trigger: "blur",
},
],
password: [
{
required: true,
message: "请输入密码",
trigger: "blur",
},
],
};
//
const login = ref(null);
@ -96,16 +109,16 @@ var loginData = ref({
//
const onLogin = async () => {
console.log("params:",param)
console.log("params:", param);
loginData.value.username = param.username;
loginData.value.password = param.password;
loginData.value.username = param.username;
loginData.value.password = param.password;
let result = await loginService(loginData);
console.log("login result:", result);
if (result.code !== 0) {
//alert(result.message);
ElMessage.error("登录失败!用户名或密码错误");
return
return;
}
globalData.token = result.data;
localStorage.setItem("token", result.data.token);
@ -115,22 +128,16 @@ loginData.value.password = param.password;
localStorage.setItem("end_time", now.setDate(now.getHours() + 12)); //
await getMyUserInfo(result.data.id);
//token.value= result.data;
ElMessage.success('登录成功');
localStorage.setItem('ms_username', result.data.username);
const keys = permiss.defaultList[result.data.username == 'admin' ? 'admin' : 'user'];
permiss.handleSet(keys);
localStorage.setItem('ms_keys', JSON.stringify(keys));
router.push("/");
};
};
const getMyUserInfo = async (id) => {
let result = {};
try {
let tokenData ={
let tokenData = {
token: localStorage.getItem("token"),
id: id,
}
};
result = await GetUserInfoService(tokenData);
if (result.code === 0) {
//console.log("token data:",this.tokenData)
@ -139,6 +146,14 @@ const getMyUserInfo = async (id) => {
localStorage.setItem("cid_func", result.data.CIDFunc);
localStorage.setItem("role", result.data.Role);
ElMessage.success("登录成功");
localStorage.setItem("ms_username", result.data.Name);
const keys =
permiss.defaultList[result.data.Role == "admin" ? "admin" : "user"];
permiss.handleSet(keys);
localStorage.setItem("ms_keys", JSON.stringify(keys));
router.push("/");
//alert("video_func:" + localStorage.getItem("video_func")+" type:" +typeof(localStorage.getItem("video_func")));
}
} catch (e) {
@ -147,72 +162,71 @@ const getMyUserInfo = async (id) => {
};
//
</script>
<style scoped>
.login-bg {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: url(../../assets/img/login-bg3.jpg) center/cover no-repeat;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background: url(../../assets/img/login-bg3.jpg) center/cover no-repeat;
}
.login-header {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40px;
}
.logo {
width: 35px;
width: 35px;
}
.login-title {
font-size: 22px;
color: #333;
font-weight: bold;
font-size: 22px;
color: #333;
font-weight: bold;
}
.login-container {
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
width: 450px;
border-radius: 5px;
background: #fff;
padding: 40px 50px 50px;
box-sizing: border-box;
}
.pwd-tips {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
margin: -10px 0 10px;
color: #787878;
}
.pwd-checkbox {
height: auto;
height: auto;
}
.login-btn {
display: block;
width: 100%;
display: block;
width: 100%;
}
.login-tips {
font-size: 12px;
color: #999;
font-size: 12px;
color: #999;
}
.login-text {
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
display: flex;
align-items: center;
margin-top: 20px;
font-size: 14px;
color: #787878;
}
</style>

View File

@ -105,6 +105,14 @@ const getMyUserInfo = async (id) => {
localStorage.setItem("cid_func", result.data.CIDFunc);
localStorage.setItem("role", result.data.Role);
ElMessage.success("注册成功");
localStorage.setItem("ms_username", result.data.Name);
const keys =
permiss.defaultList[result.data.Role == "admin" ? "admin" : "user"];
permiss.handleSet(keys);
localStorage.setItem("ms_keys", JSON.stringify(keys));
router.push("/");
//alert("video_func:" + localStorage.getItem("video_func")+" type:" +typeof(localStorage.getItem("video_func")));
}
} catch (e) {

View File

@ -8,28 +8,16 @@
</div>
<div class="user-info">
<div class="info-name">{{ name }}</div>
<div class="info-desc">
<span>@lin-xin</span>
<el-divider direction="vertical" />
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
</div>
<div class="info-desc">FE Developer</div>
<div class="info-icon">
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a>
<i class="el-icon-lx-qq-fill"></i>
<i class="el-icon-lx-facebook-fill"></i>
<i class="el-icon-lx-twitter-fill"></i>
</div>
</div>
<div class="user-footer">
<div class="user-footer-item">
<el-statistic title="Follower" :value="1800" />
<el-statistic title="提问数" :value="1800" />
</div>
<div class="user-footer-item">
<el-statistic title="Following" :value="666" />
<el-statistic title="会话数" :value="666" />
</div>
<div class="user-footer-item">
<el-statistic title="Total Post" :value="888" />
<el-statistic title="总计" :value="888" />
</div>
</div>
</el-card>
@ -39,9 +27,9 @@
: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">
<!-- <el-tab-pane name="label1" label="消息通知" class="user-tabpane">
<TabsComp />
</el-tab-pane>
</el-tab-pane> -->
<el-tab-pane name="label2" label="我的头像" class="user-tabpane">
<div class="crop-wrap" v-if="activeName === 'label2'">
<vueCropper
@ -72,24 +60,13 @@
<el-input type="password" v-model="form.new1"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">保存</el-button>
<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" label="赞赏作者" class="user-tabpane">
<div class="plugins-tips">
如果该框架
<el-link href="https://github.com/lin-xin/vue-manage-system" target="_blank"
>vue-manage-system</el-link
>
对你有帮助那就请作者喝杯饮料吧<el-icon>
<ColdDrink />
</el-icon>
加微信号 linxin_20 探讨问题
</div>
<div>
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
</div>
<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-tabs>
</el-card>
@ -103,6 +80,15 @@ 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 { UploadFileService } from "@/api/tool";
import { UserInfo } 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";
const name = localStorage.getItem('ms_username');
const form = reactive({
@ -111,17 +97,31 @@ const form = reactive({
old: '',
});
const onSubmit = () => {};
const userInfo = ref<UserInfo>({});
const isUserInfoLoaded = ref(false);
const activeName = ref('label1');
const avatarImg = ref(avatar);
const imgSrc = ref(avatar);
const activeName = ref('label2');
const router = useRouter();
const avatarImg = ref('');
const imgSrc = ref('');
const cropImg = ref('');
const cropper: any = ref();
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();
@ -132,12 +132,147 @@ const setImage = (e: any) => {
reader.readAsDataURL(file);
};
const updateUserInfo = async (data: any) => {
let result ={}
try{
let req={};
req.token=localStorage.getItem("token");
//
req.id = data.ID;
req.name = data.Name;
req.age = data.Age;
req.gender = data.Gender;
req.password = data.Password;
req.email = data.Email;
req.avatar = data.Avatar;
req.Role = data.Role;
result = await updateUserInfoService(req)
if (result.code === 0) {
ElMessage.success("更新成功");
this.updateDialogVisible = false;
} 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 ,adminuser
{ 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();
};
const saveAvatar = () => {
avatarImg.value = cropImg.value;
// 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 saveAvatar =async () => {
let token = localStorage.getItem('token');
avatarImg.value = cropImg.value;
let formData = new FormData();
//
let file= dataURLtoFile(imgSrc.value, 'avatar.jpg');
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://tx.ljsea.top/tool/file/"+resp_data.FileStoreName;
userInfo.value.Avatar = url;
avatarImg.value = url;
//
await updateUserInfo(userInfo.value);
};
</script>

View File

@ -1,253 +0,0 @@
<template>
<div class="user-container">
<el-card class="user-profile mgb20" 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 class="info-desc">
<!-- <span>{{ name }}</span>
<el-divider direction="vertical" /> -->
<span>FE Developer</span>
<el-divider direction="vertical" />
<el-link href="https://lin-xin.gitee.io" target="_blank">lin-xin.gitee.io</el-link>
</div>
<!-- <div class="info-icon">
<a href="https://github.com/lin-xin" target="_blank"> <i class="el-icon-lx-github-fill"></i></a>
<i class="el-icon-lx-qq-fill"></i>
<i class="el-icon-lx-facebook-fill"></i>
<i class="el-icon-lx-twitter-fill"></i>
</div> -->
</div>
<!-- <div class="user-footer">
<div class="user-footer-item">
<el-statistic title="Follower" value="18K" />
</div>
<div class="user-footer-item">
<el-statistic title="Following" :value="666" />
</div>
<div class="user-footer-item">
<el-statistic title="Total Post" :value="888" />
</div>
</div> -->
</el-card>
<el-card class="user-content" shadow="hover"
:body-style="{ padding: '20px 50px', height: '100%', boxSizing: 'border-box' }">
<el-tabs 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="onSubmit">保存</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<el-tab-pane name="label4" label="赞赏作者" class="user-tabpane">
<div class="plugins-tips">
如果该框架 <el-link href="https://github.com/lin-xin/vue-manage-system"
target="_blank">vue-manage-system</el-link> <el-icon>
<ColdDrink />
</el-icon> linxin_20
</div>
<div>
<img src="https://lin-xin.gitee.io/images/weixin.jpg" />
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
<script setup lang="ts" name="ucenter">
import { reactive, ref } 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';
const name = localStorage.getItem('ms_username');
const form = reactive({
new1: '',
new: '',
old: ''
});
const onSubmit = () => { };
const activeName = ref('label1');
const avatarImg = ref(avatar);
const imgSrc = ref(avatar);
const cropImg = ref('');
const cropper: any = ref();
const setImage = (e: any) => {
const file = e.target.files[0];
if (!file.type.includes('image/')) {
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 cropImage = () => {
cropImg.value = cropper.value?.getCroppedCanvas().toDataURL();
};
const saveAvatar = () => {
avatarImg.value = cropImg.value;
};
</script>
<style scoped>
/* .user-container {
display: flex;
} */
.user-profile-bg {
width: 100%;
height: 150px;
background-image: url('../../assets/img/bahnhofsidylle.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.user-profile {
position: relative;
/* width: 500px; */
/* margin-right: 20px; */
/* flex: 0 0 auto;
align-self: flex-start;
} */
}
.user-avatar-wrap {
position: absolute;
top: 90px;
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, .16)
}
.user-info {
text-align: center;
padding: 70px 0 20px;
}
.info-name {
margin: 0 0 10px;
font-size: 22px;
font-weight: 500;
color: #373a3c;
}
.info-desc {
display: flex;
align-items: center;
justify-content: center;
}
.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);
}
</style>
<style>
.el-tabs.el-tabs--left {
height: 100%;
}
</style>

View File

@ -5,14 +5,18 @@
<TableCustom :columns="columns" :tableData="tableData" :total="page.total" :viewFunc="handleView"
:delFunc="handleDelete" :page-change="changePage" :editFunc="handleEdit">
<template #toolbarBtn>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible = true">新增</el-button>
<el-button type="warning" :icon="CirclePlusFilled" @click="visible_add = true">新增</el-button>
</template>
</TableCustom>
</div>
<el-dialog :title="isEdit ? '编辑' : '新增'" v-model="visible" width="700px" destroy-on-close
<el-dialog :title="isEdit ? '编辑': '编辑'" v-model="visible" width="700px" destroy-on-close
:close-on-click-modal="false" @close="closeDialog">
<TableEdit :form-data="rowData" :options="options" :edit="isEdit" :update="updateData" />
<TableEdit :form-data="rowData" :options="options_edit" :edit="isEdit" :update="updateData" />
</el-dialog>
<el-dialog :title="isAdd ? '新增' : '新增'" v-model="visible_add" width="700px" destroy-on-close
:close-on-click-modal="false" @close="closeDialog">
<TableEdit :form-data="rowData" :options="options" :edit="isAdd" :update="addData" />
</el-dialog>
<el-dialog title="查看详情" v-model="visible1" width="700px" destroy-on-close>
<TableDetail :data="viewData"></TableDetail>
@ -24,8 +28,11 @@
import { ref, reactive } from 'vue';
import { ElMessage } from 'element-plus';
import { CirclePlusFilled } from '@element-plus/icons-vue';
import { User } from '@/types/user';
import { UserInfo } from '@/types/user';
import { fetchUserData } from '@/api';
import { SearchUserService } from "@/api/user";
import {GetUserInfoService} from "@/api/user";
import {updateUserInfoService} from "@/api/user";
import TableCustom from '@/components/table-custom.vue';
import TableDetail from '@/components/table-detail.vue';
import TableSearch from '@/components/table-search.vue';
@ -36,30 +43,59 @@ const query = reactive({
name: '',
});
const searchOpt = ref<FormOptionList[]>([
{ type: 'input', label: '用户名', prop: 'name' }
{ type: 'input', label: '用户名或ID', prop: 'name' }
])
const handleSearch = () => {
changePage(1);
const handleSearch = async () => {
let search_id= -1;
let keyword_ = "";
//search_id
if(isNaN(query.name)){
//
keyword_ = query.name;
}else if(isFinite(query.name)){
//ID
search_id = parseInt(query.name);
}else{
//
ElMessage.error("输入错误,请输入数字或者关键字");
return;
}
let req={
token: localStorage.getItem('token'),
id: search_id,
keyword: keyword_,
}
let result = await SearchUserService(req);
tableData.value = result.data;
page.total = result.data.length;
};
//
let columns = ref([
{ type: 'index', label: '序号', width: 55, align: 'center' },
{ prop: 'name', label: '用户名' },
{ prop: 'phone', label: '手机号' },
{ prop: 'role', label: '角色' },
{ prop: 'ID', label: '用户ID' },
{ prop: 'Name', label: '用户名' },
{ prop: 'Age', label: '年龄'},
{ prop: 'Role', label: '角色' },
{ prop: 'CreatedAt', label: '创建时间',type: 'date' },
{ prop: 'Email', label: '邮箱' },
{ prop: 'operator', label: '操作', width: 250 },
])
const page = reactive({
index: 1,
size: 10,
total: 0,
total: 122,
})
const tableData = ref<User[]>([]);
const tableData = ref<UserInfo[]>([]);
const getData = async () => {
const res = await fetchUserData()
tableData.value = res.data.list;
page.total = res.data.pageTotal;
let req={
token: localStorage.getItem('token'),
id: -1,
keyword: "_121",
}
let result = await SearchUserService(req);
tableData.value = result.data;
page.total = result.data.length;
};
getData();
@ -68,33 +104,100 @@ const changePage = (val: number) => {
getData();
};
// /
//
let options = ref<FormOption>({
labelWidth: '100px',
span: 12,
list: [
{ type: 'input', label: '用户名', prop: 'name', required: true },
{ type: 'input', label: '手机号', prop: 'phone', required: true },
{ type: 'input', label: '密码', prop: 'password', required: true },
{ type: 'input', label: '邮箱', prop: 'email', required: true },
{ type: 'input', label: '角色', prop: 'role', required: true },
{ type: 'input', label: '用户名', prop: 'Name', required: true },
{ type: 'input', label: '密码', prop: 'Password', required: true },
{ type: 'input', label: '邮箱', prop: 'Email', required: true },
{ type: 'input', label: '角色', prop: 'Role', required: true },
]
})
//
let options_edit = ref<FormOption>({
labelWidth: '100px',
span: 12,
list: [
{prop: 'Avatar',label: '头像', type: 'input', required: false},
{ type: 'input', label: '用户名', prop: 'Name', required: true },
{ type: 'input', label: '年龄', prop: 'Age', required: false },
{ type: 'input', label: '密码', prop: 'Password', required: false },
{ type: 'input', label: '邮箱', prop: 'Email', required: true },
{ type: 'input', label: '性别', prop: 'Gender', required: false },
//select ,adminuser
{ type: 'select', label: '角色', prop: 'Role', opts: [{label:"管理员",value:"admin"},{label:"普通用户",value:"user"}],required: false },
]
})
const visible = ref(false);
const visible_add = ref(false);
const isEdit = ref(false);
const isAdd = ref(false);
const rowData = ref({});
const handleEdit = (row: User) => {
rowData.value = { ...row };
const handleEdit = async (row: UserInfo) => {
let data = await getUserInfo(row.ID);
rowData.value = data;
isEdit.value = true;
visible.value = true;
};
const updateData = () => {
closeDialog();
getData();
const updateData = async (data) => {
let result ={}
try{
let req={};
req.token=localStorage.getItem("token");
//console.log(rowData.value);
//
req.id = data.ID;
req.name = data.Name;
req.age = data.Age;
req.gender = data.Gender;
req.password = data.Password;
req.email = data.Email;
req.avatar = data.Avatar;
req.Role = data.Role;
result = await updateUserInfoService(req)
if (result.code === 0) {
ElMessage.success("更新成功");
this.updateDialogVisible = false;
} else {
ElMessage.error("更新失败");
}
}catch(e){
console.log(e);
}
closeDialog();
handleSearch();
};
const getUserInfo = async (id) => {
let result = {};
try{
//
let req={
token: localStorage.getItem('token'),
id: id,
};
result = await GetUserInfoService(req)
if(result.code===0){
return result.data;
}
}catch(e){
console.log(e);
}
return {};
}
const closeDialog = () => {
visible.value = false;
visible_add.value = false;
isEdit.value = false;
};
@ -104,43 +207,47 @@ const viewData = ref({
row: {},
list: []
});
const handleView = (row: User) => {
viewData.value.row = { ...row }
const handleView =async (row: UserInfo) => {
let data = await getUserInfo(row.ID);
viewData.value.row = data;
viewData.value.list = [
{
prop: 'id',
prop: 'Avatar',
label: '头像', //
type: 'image',
width: 100,
},
{
prop: 'ID',
label: '用户ID',
},
{
prop: 'name',
prop: 'Name',
label: '用户名',
},
{
prop: 'password',
label: '密码',
},
{
prop: 'email',
prop: 'Email',
label: '邮箱',
},
{
prop: 'phone',
label: '电话',
prop: 'Gender',
label: '性别',
},
{
prop: 'role',
prop: 'Role',
label: '角色',
},
{
prop: 'date',
prop: 'CreatedAt',
label: '注册日期',
type: 'date',
},
]
visible1.value = true;
};
//
const handleDelete = (row: User) => {
const handleDelete = (row: UserInfo) => {
ElMessage.success('删除成功');
}
</script>

121
yarn.lock
View File

@ -19,6 +19,105 @@
dependencies:
regenerator-runtime "^0.14.0"
"@cropper/element-canvas@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.0.0.tgz"
integrity sha512-GPtGJgSm92crJhhhwUsaMw3rz2KfJWWSz7kRAlufFEV/EHTP5+6r6/Z1BCGRna830i+Avqbm435XLOtA7PVJwA==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-crosshair@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.0.0.tgz"
integrity sha512-KfPfyrdeFvUC31Ws7ATtcalWWSaMtrC6bMoCipZhqbUOE7wZoL4ecDSL6BUOZxPa74awZUqfzirCDjHvheBfyw==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-grid@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.0.0.tgz"
integrity sha512-i78SQ0IJTLFveKX6P7svkfMYVdgHrQ8ZmmEw8keFy9n1ZVbK+SK0UHK5FNMRNI/gtVhKJOGEnK/zeyjUdj4Iyw==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-handle@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.0.0.tgz"
integrity sha512-ZJvW+0MkK9E8xYymGdoruaQn2kwjSHFpNSWinjyq6csuVQiCPxlX5ovAEDldmZ9MWePPtWEi3vLKQOo2Yb0T8g==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-image@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.0.0.tgz"
integrity sha512-9BxiTS/aHRmrjopaFQb9mQQXmx4ruhYHGkDZMVz24AXpMFjUY6OpqrWse/WjzD9tfhMFvEdu17b3VAekcAgpeg==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/element-canvas" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-selection@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.0.0.tgz"
integrity sha512-ensNnbIfJsJ8bhbJTH/RXtk2URFvTOO4TvfRk461n2FPEC588D7rwBmUJxQg74IiTi4y1JbCI+6j+4LyzYBLCQ==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/element-canvas" "^2.0.0"
"@cropper/element-image" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-shade@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.0.0.tgz"
integrity sha512-jv/2bbNZnhU4W+T4G0c8ADocLIZvQFTXgCf2RFDNhI5UVxurzWBnDdb8Mx8LnVplnkTqO+xUmHZYve0CwgWo+Q==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/element-canvas" "^2.0.0"
"@cropper/element-selection" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element-viewer@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.0.0.tgz"
integrity sha512-zY+3VRN5TvpM8twlphYtXw0tzJL2VgzeK7ufhL1BixVqOdRxwP13TprYIhqwGt9EW/SyJZUiaIu396T89kRX8A==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/element-canvas" "^2.0.0"
"@cropper/element-image" "^2.0.0"
"@cropper/element-selection" "^2.0.0"
"@cropper/utils" "^2.0.0"
"@cropper/element@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/element/-/element-2.0.0.tgz"
integrity sha512-lsthn0nQq73GExUE7Mg/ss6Q3RXADGDv055hxoLFwvl/wGHgy6ZkYlfLZ/VmgBHC6jDK5IgPBFnqrPqlXWSGBA==
dependencies:
"@cropper/utils" "^2.0.0"
"@cropper/elements@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/elements/-/elements-2.0.0.tgz"
integrity sha512-PQkPo1nUjxLFUQuHYu+6atfHxpX9B41Xribao6wpvmvmNIFML6LQdNqqWYb6LyM7ujsu71CZdBiMT5oetjJVoQ==
dependencies:
"@cropper/element" "^2.0.0"
"@cropper/element-canvas" "^2.0.0"
"@cropper/element-crosshair" "^2.0.0"
"@cropper/element-grid" "^2.0.0"
"@cropper/element-handle" "^2.0.0"
"@cropper/element-image" "^2.0.0"
"@cropper/element-selection" "^2.0.0"
"@cropper/element-shade" "^2.0.0"
"@cropper/element-viewer" "^2.0.0"
"@cropper/utils@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@cropper/utils/-/utils-2.0.0.tgz"
integrity sha512-cprLYr+7kK3faGgoOsTW9gIn5sefDr2KwOmgyjzIXk+8PLpW8FgFKEg5FoWfRD5zMAmkCBuX6rGKDK3VdUEGrg==
"@ctrl/tinycolor@^3.4.1":
version "3.6.1"
resolved "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
@ -498,6 +597,19 @@ crc-32@~1.2.0, crc-32@~1.2.1:
resolved "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
cropperjs@^1.5.6:
version "1.6.2"
resolved "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.2.tgz"
integrity sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==
cropperjs@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/cropperjs/-/cropperjs-2.0.0.tgz"
integrity sha512-TO2j0Qre01kPHbow4FuTrbdEB4jTmGRySxW49jyEIqlJZuEBfrvCTT0vC3eRB2WBXudDfKi1Onako6DKWKxeAQ==
dependencies:
"@cropper/elements" "^2.0.0"
"@cropper/utils" "^2.0.0"
csstype@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
@ -1282,6 +1394,13 @@ vue-cropper@1.1.1:
resolved "https://registry.npmjs.org/vue-cropper/-/vue-cropper-1.1.1.tgz"
integrity sha512-WsqKMpaBf9Osi1LQlE/5AKdD0nHWOy1asLXocaG8NomOWO07jiZi968+/PbMmnD0QbPJOumDQaGuGa13qys85A==
vue-cropperjs@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/vue-cropperjs/-/vue-cropperjs-5.0.0.tgz"
integrity sha512-RhnC8O33uRZNkn74aiHZwNHnBJOXWlS4P6gsRI0lw4cZlWjKSCywZI9oSI9POlIPI6OYv30jvnHMXGch85tw7w==
dependencies:
cropperjs "^1.5.6"
vue-demi@*:
version "0.14.7"
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz"
@ -1326,7 +1445,7 @@ vue-tsc@^0.38.4:
dependencies:
"@volar/vue-typescript" "0.38.9"
"vue@^2.6.12 || ^3.1.1", "vue@^2.6.14 || ^3.3.0", "vue@^3.0.0-0 || ^2.6.0", vue@^3.0.5, vue@^3.2.0, vue@^3.2.25, vue@^3.2.47, vue@^3.4.5, "vue@2 || 3", vue@3.4.21:
"vue@^2.6.12 || ^3.1.1", "vue@^2.6.14 || ^3.3.0", "vue@^3.0.0-0 || ^2.6.0", vue@^3.0.5, vue@^3.2.0, vue@^3.2.25, vue@^3.2.47, vue@^3.4.5, vue@>=3.0.0, "vue@2 || 3", vue@3.4.21:
version "3.4.21"
resolved "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz"
integrity sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==