mirror of https://gitee.com/godoos/godoos.git
13 changed files with 7634 additions and 720 deletions
File diff suppressed because it is too large
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 4.5 KiB |
@ -1,314 +1,595 @@ |
|||
<template> |
|||
<div class="lockscreen" :class="lockClassName"> |
|||
<el-card class="login-box" shadow="never"> |
|||
<div class="avatar-container"> |
|||
<el-avatar size="large"> |
|||
<img src="/logo.png" alt="Logo" /> |
|||
</el-avatar> |
|||
</div> |
|||
<el-form v-if="!isRegisterMode" label-position="left" label-width="0px"> |
|||
<el-form-item> |
|||
<el-input |
|||
v-model="userName" |
|||
placeholder="请输入用户名" |
|||
autofocus |
|||
prefix-icon="UserFilled" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item v-if="!sys._options.noPassword"> |
|||
<el-input |
|||
v-model="userPassword" |
|||
type="password" |
|||
placeholder="请输入登录密码" |
|||
show-password |
|||
prefix-icon="Key" |
|||
@keyup.enter="onLogin" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-button type="primary" @click="onLogin">登录</el-button> |
|||
<div class="actions" v-if="config.userType === 'member'"> |
|||
<a href="#" @click.prevent="toggleRegister">注册新用户</a> |
|||
<a href="#" @click.prevent="toggleUserSwitch">切换角色</a> |
|||
</div> |
|||
</el-form> |
|||
<el-form v-else label-position="left" label-width="0px" :model="regForm" ref="regFormRef" :rules="rules"> |
|||
<el-form-item prop="username"> |
|||
<el-input |
|||
v-model="regForm.username" |
|||
placeholder="请输入用户名" |
|||
prefix-icon="UserFilled" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="nickname"> |
|||
<el-input |
|||
v-model="regForm.nickname" |
|||
placeholder="请输入真实姓名" |
|||
prefix-icon="Avatar" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="email"> |
|||
<el-input |
|||
v-model="regForm.email" |
|||
placeholder="请输入邮箱" |
|||
prefix-icon="Message" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="phone"> |
|||
<el-input |
|||
v-model="regForm.phone" |
|||
placeholder="请输入手机号" |
|||
prefix-icon="Iphone" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="password"> |
|||
<el-input |
|||
v-model="regForm.password" |
|||
type="password" |
|||
placeholder="请输入密码" |
|||
show-password |
|||
prefix-icon="Key" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="confirmPassword"> |
|||
<el-input |
|||
v-model="regForm.confirmPassword" |
|||
type="password" |
|||
placeholder="请再次输入密码" |
|||
show-password |
|||
prefix-icon="Lock" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-button type="primary" @click="onRegister">注册</el-button> |
|||
<div class="actions"> |
|||
<a href="#" @click.prevent="toggleRegister">返回登录</a> |
|||
</div> |
|||
</el-form> |
|||
</el-card> |
|||
</div> |
|||
<div |
|||
class="lockscreen" |
|||
:class="lockClassName" |
|||
> |
|||
<el-card |
|||
class="login-box" |
|||
shadow="never" |
|||
> |
|||
<div class="avatar-container"> |
|||
<el-avatar size="large"> |
|||
<img |
|||
src="/logo.png" |
|||
alt="Logo" |
|||
/> |
|||
</el-avatar> |
|||
</div> |
|||
<el-form |
|||
v-if="!isRegisterMode" |
|||
label-position="left" |
|||
label-width="0px" |
|||
> |
|||
<el-form-item> |
|||
<el-input |
|||
v-model="userName" |
|||
placeholder="请输入用户名" |
|||
autofocus |
|||
prefix-icon="UserFilled" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item v-if="!sys._options.noPassword"> |
|||
<el-input |
|||
v-model="userPassword" |
|||
type="password" |
|||
placeholder="请输入登录密码" |
|||
show-password |
|||
prefix-icon="Key" |
|||
@keyup.enter="onLogin" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-button |
|||
type="primary" |
|||
@click="onLogin" |
|||
>登录</el-button |
|||
> |
|||
<div class="divider"> |
|||
<span>第三方登录</span> |
|||
</div> |
|||
<div class="third-party-login"> |
|||
<img |
|||
style=" |
|||
width: 25px; |
|||
height: 25px; |
|||
cursor: pointer; |
|||
color: #409eff; |
|||
" |
|||
src="../../../public/image/login/微信.png" |
|||
@click="onThirdPartyLogin('wechat')" |
|||
/> |
|||
<img |
|||
style=" |
|||
width: 25px; |
|||
height: 25px; |
|||
cursor: pointer; |
|||
color: #409eff; |
|||
" |
|||
src="../../../public/image/login/QQ.png" |
|||
@click="onThirdPartyLogin('qq')" |
|||
/> |
|||
<img |
|||
style=" |
|||
width: 25px; |
|||
height: 25px; |
|||
cursor: pointer; |
|||
color: #409eff; |
|||
" |
|||
src="../../../public/image/login/新浪微博.png" |
|||
@click="onThirdPartyLogin('sina')" |
|||
/> |
|||
<img |
|||
style=" |
|||
width: 25px; |
|||
height: 25px; |
|||
cursor: pointer; |
|||
color: #409eff; |
|||
" |
|||
src="../../../public/image/login/GitHub.png" |
|||
@click="onThirdPartyLogin('github')" |
|||
/> |
|||
<img |
|||
style=" |
|||
width: 25px; |
|||
height: 25px; |
|||
cursor: pointer; |
|||
color: #409eff; |
|||
" |
|||
src="../../../public/image/login/gitee.png" |
|||
@click="onThirdPartyLogin('gitee')" |
|||
/> |
|||
</div> |
|||
<div |
|||
class="actions" |
|||
v-if="config.userType === 'member'" |
|||
> |
|||
<a |
|||
href="#" |
|||
@click.prevent="toggleRegister" |
|||
>注册新用户</a |
|||
> |
|||
<a |
|||
href="#" |
|||
@click.prevent="toggleUserSwitch" |
|||
>切换角色</a |
|||
> |
|||
</div> |
|||
</el-form> |
|||
<el-form |
|||
v-else |
|||
label-position="left" |
|||
label-width="0px" |
|||
:model="regForm" |
|||
ref="regFormRef" |
|||
:rules="rules" |
|||
> |
|||
<el-form-item prop="username"> |
|||
<el-input |
|||
v-model="regForm.username" |
|||
placeholder="请输入用户名" |
|||
prefix-icon="UserFilled" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="nickname"> |
|||
<el-input |
|||
v-model="regForm.nickname" |
|||
placeholder="请输入真实姓名" |
|||
prefix-icon="Avatar" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="email"> |
|||
<el-input |
|||
v-model="regForm.email" |
|||
placeholder="请输入邮箱" |
|||
prefix-icon="Message" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="phone"> |
|||
<el-input |
|||
v-model="regForm.phone" |
|||
placeholder="请输入手机号" |
|||
prefix-icon="Iphone" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="password"> |
|||
<el-input |
|||
v-model="regForm.password" |
|||
type="password" |
|||
placeholder="请输入密码" |
|||
show-password |
|||
prefix-icon="Key" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-form-item prop="confirmPassword"> |
|||
<el-input |
|||
v-model="regForm.confirmPassword" |
|||
type="password" |
|||
placeholder="请再次输入密码" |
|||
show-password |
|||
prefix-icon="Lock" |
|||
></el-input> |
|||
</el-form-item> |
|||
<el-button |
|||
type="primary" |
|||
@click="onRegister" |
|||
>注册</el-button |
|||
> |
|||
<div class="actions"> |
|||
<a |
|||
href="#" |
|||
@click.prevent="toggleRegister" |
|||
>返回登录</a |
|||
> |
|||
</div> |
|||
</el-form> |
|||
</el-card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, onMounted } from 'vue'; |
|||
import { useSystem } from '@/system'; |
|||
import { getSystemConfig, setSystemConfig } from '@/system/config'; |
|||
import { notifyError } from '@/util/msg'; |
|||
import { RestartApp } from '@/util/goutil'; |
|||
|
|||
const sys = useSystem(); |
|||
const loginCallback = sys._options.loginCallback; |
|||
const config = getSystemConfig(); |
|||
const lockClassName = ref('screen-show'); |
|||
const isRegisterMode = ref(false); |
|||
|
|||
function loginSuccess() { |
|||
lockClassName.value = 'screen-hidean'; |
|||
setTimeout(() => { |
|||
lockClassName.value = 'screen-hide'; |
|||
}, 500); |
|||
} |
|||
|
|||
const userName = ref(''); |
|||
const userPassword = ref(''); |
|||
|
|||
onMounted(() => { |
|||
if (config.userType === 'person') { |
|||
userName.value = sys._options.login?.username || 'admin'; |
|||
userPassword.value = sys._options.login?.password || ''; |
|||
} else { |
|||
userName.value = config.userInfo.username; |
|||
userPassword.value = config.userInfo.password; |
|||
} |
|||
}); |
|||
|
|||
async function onLogin() { |
|||
localStorage.removeItem("godoosClientId"); |
|||
if (loginCallback) { |
|||
const res = await loginCallback(userName.value, userPassword.value); |
|||
if (res) { |
|||
loginSuccess(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
function toggleRegister() { |
|||
isRegisterMode.value = !isRegisterMode.value; |
|||
} |
|||
|
|||
function toggleUserSwitch() { |
|||
config.userType = 'person' |
|||
setSystemConfig(config); |
|||
RestartApp(); |
|||
} |
|||
const regForm = ref({ |
|||
username: '', |
|||
password: '', |
|||
confirmPassword: '', |
|||
email: '', |
|||
phone: '', |
|||
nickname: '', |
|||
}) |
|||
const regFormRef:any = ref(null); |
|||
|
|||
const rules = { |
|||
username: [ |
|||
{ required: true, message: '用户名不能为空', trigger: 'blur' }, |
|||
{ min: 3, max: 20, message: '用户名长度应在3到20个字符之间', trigger: 'blur' } |
|||
], |
|||
password: [ |
|||
{ required: true, message: '密码不能为空', trigger: 'blur' }, |
|||
{ min: 6, message: '密码长度不能小于6位', trigger: 'blur' } |
|||
], |
|||
confirmPassword: [ |
|||
{ required: true, message: '请再次输入密码', trigger: 'blur' }, |
|||
{ validator: (rule:any, value:any, callback:any) => { |
|||
console.log(rule) |
|||
if (value === '') { |
|||
callback(new Error('请再次输入密码')); |
|||
} else if (value !== regForm.value.password) { |
|||
callback(new Error('两次输入的密码不一致')); |
|||
} else { |
|||
callback(); |
|||
} |
|||
}, trigger: 'blur' } |
|||
], |
|||
email: [ |
|||
{ required: true, message: '邮箱不能为空', trigger: 'blur' }, |
|||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: ['blur', 'change'] } |
|||
], |
|||
phone: [ |
|||
{ required: true, message: '手机号不能为空', trigger: 'blur' }, |
|||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入有效的手机号', trigger: 'blur' } |
|||
], |
|||
nickname: [ |
|||
{ required: true, message: '昵称不能为空', trigger: 'blur' }, |
|||
{ min: 2, max: 20, message: '昵称长度应在2到20个字符之间', trigger: 'blur' } |
|||
] |
|||
}; |
|||
|
|||
async function onRegister() { |
|||
try { |
|||
await regFormRef.value.validate(); |
|||
const save = toRaw(regForm.value); |
|||
const userInfo = config.userInfo; |
|||
const comp = await fetch(userInfo.url + '/member/register', { |
|||
method: 'POST', |
|||
body: JSON.stringify(save), |
|||
}); |
|||
if(!comp.ok){ |
|||
notifyError('网络错误,注册失败'); |
|||
return |
|||
} |
|||
const res = await comp.json(); |
|||
if(res.success){ |
|||
notifyError('注册成功'); |
|||
toggleRegister(); |
|||
}else{ |
|||
notifyError(res.message); |
|||
return |
|||
} |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
|
|||
} |
|||
import { useLoginStore } from "@/stores/login"; |
|||
import { useSystem } from "@/system"; |
|||
import { getSystemConfig, setSystemConfig } from "@/system/config"; |
|||
import router from "@/system/router"; |
|||
import { RestartApp } from "@/util/goutil"; |
|||
import { notifyError } from "@/util/msg"; |
|||
import { onMounted, ref } from "vue"; |
|||
const store = useLoginStore(); |
|||
const sys = useSystem(); |
|||
const loginCallback = sys._options.loginCallback; |
|||
const config = getSystemConfig(); |
|||
const lockClassName = ref("screen-show"); |
|||
const isRegisterMode = ref(false); |
|||
function loginSuccess() { |
|||
lockClassName.value = "screen-hidean"; |
|||
setTimeout(() => { |
|||
lockClassName.value = "screen-hide"; |
|||
}, 500); |
|||
} |
|||
|
|||
const userName = ref(""); |
|||
const userPassword = ref(""); |
|||
|
|||
onMounted(() => { |
|||
if (config.userType === "person") { |
|||
userName.value = sys._options.login?.username || "admin"; |
|||
userPassword.value = sys._options.login?.password || ""; |
|||
} else { |
|||
userName.value = config.userInfo.username; |
|||
userPassword.value = config.userInfo.password; |
|||
} |
|||
}); |
|||
|
|||
watch( |
|||
() => router.currentRoute.value.query.code, |
|||
() => { |
|||
onLogin(); |
|||
} |
|||
); |
|||
|
|||
const onLogin = async () => { |
|||
localStorage.removeItem("godoosClientId"); |
|||
if (loginCallback) { |
|||
const platform = store.ThirdPartyPlatform; |
|||
const code = router.currentRoute.value.query.code as string; |
|||
|
|||
// 使用映射对象,将平台名称映射到相应的参数名称 |
|||
const platformCodeMap: Record<string, string> = { |
|||
github: "github_code", |
|||
gitee: "gitee_code", |
|||
wechat: "wechat_code", |
|||
qq: "qq_code", |
|||
}; |
|||
|
|||
// 获取对应的参数名称 |
|||
const codeParam = platform ? platformCodeMap[platform] : null; |
|||
|
|||
let res; |
|||
if (codeParam) { |
|||
// 第三方登录统一调用 |
|||
const returnedState = router.currentRoute.value.query |
|||
.state as string; |
|||
|
|||
if (returnedState !== store.State) { |
|||
notifyError("登录失败,请重试"); |
|||
return; |
|||
} |
|||
|
|||
res = await loginCallback(userName.value, userPassword.value, { |
|||
[codeParam]: code, |
|||
}); |
|||
} else { |
|||
// 普通登录 |
|||
res = await loginCallback(userName.value, userPassword.value); |
|||
} |
|||
|
|||
if (res) { |
|||
store.ThirdPartyPlatform = null; |
|||
loginSuccess(); |
|||
} else { |
|||
notifyError("登录失败,请重试"); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const toggleRegister = () => { |
|||
isRegisterMode.value = !isRegisterMode.value; |
|||
}; |
|||
|
|||
const toggleUserSwitch = () => { |
|||
config.userType = "person"; |
|||
setSystemConfig(config); |
|||
RestartApp(); |
|||
}; |
|||
|
|||
const regForm = ref({ |
|||
username: "", |
|||
password: "", |
|||
confirmPassword: "", |
|||
email: "", |
|||
phone: "", |
|||
nickname: "", |
|||
}); |
|||
const regFormRef: any = ref(null); |
|||
|
|||
const rules = { |
|||
username: [ |
|||
{ required: true, message: "用户名不能为空", trigger: "blur" }, |
|||
{ |
|||
min: 3, |
|||
max: 20, |
|||
message: "用户名长度应在3到20个字符之间", |
|||
trigger: "blur", |
|||
}, |
|||
], |
|||
password: [ |
|||
{ required: true, message: "密码不能为空", trigger: "blur" }, |
|||
{ min: 6, message: "密码长度不能小于6位", trigger: "blur" }, |
|||
], |
|||
confirmPassword: [ |
|||
{ required: true, message: "请再次输入密码", trigger: "blur" }, |
|||
{ |
|||
validator: (rule: any, value: any, callback: any) => { |
|||
console.log(rule); |
|||
if (value === "") { |
|||
callback(new Error("请再次输入密码")); |
|||
} else if (value !== regForm.value.password) { |
|||
callback(new Error("两次输入的密码不一致")); |
|||
} else { |
|||
callback(); |
|||
} |
|||
}, |
|||
trigger: "blur", |
|||
}, |
|||
], |
|||
email: [ |
|||
{ required: true, message: "邮箱不能为空", trigger: "blur" }, |
|||
{ |
|||
type: "email", |
|||
message: "请输入有效的邮箱地址", |
|||
trigger: ["blur", "change"], |
|||
}, |
|||
], |
|||
phone: [ |
|||
{ required: true, message: "手机号不能为空", trigger: "blur" }, |
|||
{ |
|||
pattern: /^1[3-9]\d{9}$/, |
|||
message: "请输入有效的手机号", |
|||
trigger: "blur", |
|||
}, |
|||
], |
|||
nickname: [ |
|||
{ required: true, message: "昵称不能为空", trigger: "blur" }, |
|||
{ |
|||
min: 2, |
|||
max: 20, |
|||
message: "昵称长度应在2到20个字符之间", |
|||
trigger: "blur", |
|||
}, |
|||
], |
|||
}; |
|||
|
|||
const onRegister = async () => { |
|||
try { |
|||
await regFormRef.value.validate(); |
|||
const save = toRaw(regForm.value); |
|||
const userInfo = config.userInfo; |
|||
const comp = await fetch(userInfo.url + "/member/register", { |
|||
method: "POST", |
|||
body: JSON.stringify(save), |
|||
}); |
|||
if (!comp.ok) { |
|||
notifyError("网络错误,注册失败"); |
|||
return; |
|||
} |
|||
const res = await comp.json(); |
|||
if (res.success) { |
|||
notifyError("注册成功"); |
|||
toggleRegister(); |
|||
} else { |
|||
notifyError(res.message); |
|||
return; |
|||
} |
|||
} catch (error) { |
|||
console.error(error); |
|||
} |
|||
}; |
|||
|
|||
const onThirdPartyLogin = async (platform: string) => { |
|||
let loginFunction: (() => Promise<boolean>) | undefined; |
|||
// 当前选择的第三方登录方式 |
|||
store.ThirdPartyPlatform = platform; |
|||
switch (platform) { |
|||
case "github": |
|||
// 跳转到 GitHub 授权页面 |
|||
loginFunction = async function () { |
|||
return await authWithGithub(); |
|||
}; |
|||
break; |
|||
case "wechat": |
|||
loginFunction = async function () { |
|||
return await authWithWechat(); |
|||
}; |
|||
break; |
|||
case "qq": |
|||
loginFunction = async function () { |
|||
return await authWithQQ(); |
|||
}; |
|||
break; |
|||
case "sina": |
|||
loginFunction = async function () { |
|||
return await authWithSina(); |
|||
}; |
|||
break; |
|||
case "gitee": |
|||
loginFunction = async function () { |
|||
return await authWithGithub(); |
|||
}; |
|||
break; |
|||
default: |
|||
notifyError("不支持的第三方登录平台"); |
|||
return; |
|||
} |
|||
|
|||
if (loginFunction) { |
|||
const success = await loginFunction(); |
|||
if (success) { |
|||
loginSuccess(); |
|||
} else { |
|||
notifyError("登录失败"); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
const authWithGithub = async (): Promise<boolean> => { |
|||
// 传递state用于防止CSRF攻击,使用时间戳加随机字符串 |
|||
const state = Date.now() + Math.random().toString(36).substring(2, 15); |
|||
// 存储到本地 |
|||
store.State = state; |
|||
const url = config.userInfo.url + "/github/authorize?state=" + state; |
|||
const res: any = await fetch(url, { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
state: state, |
|||
}), |
|||
}); |
|||
if (!res.ok) { |
|||
return false; |
|||
} |
|||
const data = await res.json(); |
|||
console.log(data.data.url); |
|||
window.location.href = data.data.url; |
|||
return true; |
|||
}; |
|||
|
|||
const authWithWechat = (): boolean | PromiseLike<boolean> => { |
|||
throw new Error("Function not implemented."); |
|||
}; |
|||
|
|||
const authWithQQ = (): boolean | PromiseLike<boolean> => { |
|||
throw new Error("Function not implemented."); |
|||
}; |
|||
|
|||
const authWithSina = (): boolean | PromiseLike<boolean> => { |
|||
throw new Error("Function not implemented."); |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped lang="scss"> |
|||
.lockscreen { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
z-index: 201; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
color: #fff; |
|||
background-color: rgba(25, 28, 34, 0.78); |
|||
backdrop-filter: blur(7px); |
|||
|
|||
.login-box{ |
|||
width: 300px; |
|||
padding: 20px; |
|||
text-align: center; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
border: 1px solid #e0e0e0; |
|||
|
|||
.avatar-container { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.el-input { |
|||
width: 100%; |
|||
margin-bottom: 10px; |
|||
background: #f9f9f9; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.el-button { |
|||
width: 100%; |
|||
margin-top: 10px; |
|||
background: #409eff; |
|||
color: #ffffff; |
|||
border: none; |
|||
border-radius: 4px; |
|||
transition: background 0.3s ease; |
|||
} |
|||
|
|||
.el-button:hover { |
|||
background: #66b1ff; |
|||
} |
|||
|
|||
.tip { |
|||
padding: 4px 0; |
|||
font-size: 12px; |
|||
color: red; |
|||
height: 30px; |
|||
} |
|||
|
|||
.actions { |
|||
margin-top: 10px; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
|
|||
a { |
|||
color: #409eff; |
|||
text-decoration: none; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.screen-hidean { |
|||
animation: outan 0.5s forwards; |
|||
} |
|||
|
|||
.screen-hide { |
|||
display: none; |
|||
} |
|||
} |
|||
|
|||
@keyframes outan { |
|||
0% { |
|||
opacity: 1; |
|||
} |
|||
|
|||
30% { |
|||
opacity: 0; |
|||
} |
|||
|
|||
100% { |
|||
transform: translateY(-100%); |
|||
opacity: 0; |
|||
} |
|||
} |
|||
</style> |
|||
.lockscreen { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
z-index: 201; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
overflow: hidden; |
|||
color: #fff; |
|||
background-color: rgba(25, 28, 34, 0.78); |
|||
backdrop-filter: blur(7px); |
|||
|
|||
.login-box { |
|||
width: 300px; |
|||
padding: 20px; |
|||
text-align: center; |
|||
background: #ffffff; |
|||
border-radius: 10px; |
|||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
|||
border: 1px solid #e0e0e0; |
|||
|
|||
.avatar-container { |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.el-input { |
|||
width: 100%; |
|||
margin-bottom: 10px; |
|||
background: #f9f9f9; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.el-button { |
|||
width: 100%; |
|||
margin-top: 10px; |
|||
background: #409eff; |
|||
color: #ffffff; |
|||
border: none; |
|||
border-radius: 4px; |
|||
transition: background 0.3s ease; |
|||
} |
|||
|
|||
.el-button:hover { |
|||
background: #66b1ff; |
|||
} |
|||
|
|||
.tip { |
|||
padding: 4px 0; |
|||
font-size: 12px; |
|||
color: red; |
|||
height: 30px; |
|||
} |
|||
|
|||
.actions { |
|||
margin-top: 10px; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
|
|||
a { |
|||
color: #409eff; |
|||
text-decoration: none; |
|||
cursor: pointer; |
|||
|
|||
&:hover { |
|||
text-decoration: underline; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
.screen-hidean { |
|||
animation: outan 0.5s forwards; |
|||
} |
|||
|
|||
.screen-hide { |
|||
display: none; |
|||
} |
|||
|
|||
.third-party-login { |
|||
margin-top: 10px; |
|||
display: flex; |
|||
justify-content: center; |
|||
gap: 15px; |
|||
|
|||
el-icon { |
|||
font-size: 24px; |
|||
cursor: pointer; |
|||
transition: color 0.3s ease; |
|||
|
|||
&:hover { |
|||
color: #409eff; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.divider { |
|||
display: flex; |
|||
align-items: center; |
|||
text-align: center; |
|||
margin: 20px 0; |
|||
color: #999; |
|||
font-size: 14px; |
|||
|
|||
&::before, |
|||
&::after { |
|||
content: ""; |
|||
flex: 1; |
|||
border-bottom: 1px solid #ddd; |
|||
} |
|||
|
|||
&::before { |
|||
margin-right: 0.25em; |
|||
} |
|||
|
|||
&::after { |
|||
margin-left: 0.25em; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@keyframes outan { |
|||
0% { |
|||
opacity: 1; |
|||
} |
|||
|
|||
30% { |
|||
opacity: 0; |
|||
} |
|||
|
|||
100% { |
|||
transform: translateY(-100%); |
|||
opacity: 0; |
|||
} |
|||
} |
|||
</style> |
|||
|
@ -1,422 +1,500 @@ |
|||
<template> |
|||
<div class="container"> |
|||
<div class="nav"> |
|||
<ul> |
|||
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)" |
|||
:class="{ active: index === activeIndex }"> |
|||
{{ item }} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<div class="setting"> |
|||
<div v-if="1 === activeIndex"> |
|||
<div class="setting-item" style="margin-top: 60px"> |
|||
<label>存储方式</label> |
|||
<el-select v-model="config.storeType"> |
|||
<el-option v-for="(item, key) in storeList" :key="key" :label="item.title" :value="item.value" /> |
|||
</el-select> |
|||
</div> |
|||
<div class="setting-item" v-if="config.storeType === 'local'"> |
|||
<label>存储地址</label> |
|||
<el-input v-model="config.storePath" @click="selectFile()" placeholder="可为空,为空则取系统默认存储地址" /> |
|||
</div> |
|||
<template v-if="config.storeType === 'net'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input v-model="config.storenet.url" placeholder="http://192.168.1.16:56780 不要加斜杠" /> |
|||
</div> |
|||
</template> |
|||
<template v-if="config.storeType === 'webdav'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input v-model="config.webdavClient.url" placeholder="https://godoos.com/webdav 不要加斜杠" /> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>登陆用户名</label> |
|||
<el-input v-model="config.webdavClient.username" /> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>登陆密码</label> |
|||
<el-input v-model="config.webdavClient.password" type="password" /> |
|||
</div> |
|||
</template> |
|||
<div class="container"> |
|||
<div class="nav"> |
|||
<ul> |
|||
<li |
|||
v-for="(item, index) in items" |
|||
:key="index" |
|||
@click="selectItem(index)" |
|||
:class="{ active: index === activeIndex }" |
|||
> |
|||
{{ item }} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
<div class="setting"> |
|||
<div v-if="1 === activeIndex"> |
|||
<div |
|||
class="setting-item" |
|||
style="margin-top: 60px" |
|||
> |
|||
<label>存储方式</label> |
|||
<el-select v-model="config.storeType"> |
|||
<el-option |
|||
v-for="(item, key) in storeList" |
|||
:key="key" |
|||
:label="item.title" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</div> |
|||
<div |
|||
class="setting-item" |
|||
v-if="config.storeType === 'local'" |
|||
> |
|||
<label>存储地址</label> |
|||
<el-input |
|||
v-model="config.storePath" |
|||
@click="selectFile()" |
|||
placeholder="可为空,为空则取系统默认存储地址" |
|||
/> |
|||
</div> |
|||
<template v-if="config.storeType === 'net'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input |
|||
v-model="config.storenet.url" |
|||
placeholder="http://192.168.1.16:56780 不要加斜杠" |
|||
/> |
|||
</div> |
|||
</template> |
|||
<template v-if="config.storeType === 'webdav'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input |
|||
v-model="config.webdavClient.url" |
|||
placeholder="https://godoos.com/webdav 不要加斜杠" |
|||
/> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>登陆用户名</label> |
|||
<el-input v-model="config.webdavClient.username" /> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>登陆密码</label> |
|||
<el-input |
|||
v-model="config.webdavClient.password" |
|||
type="password" |
|||
/> |
|||
</div> |
|||
</template> |
|||
|
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button @click="submitOsInfo" type="primary"> |
|||
{{ t("confirm") }} |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button |
|||
@click="submitOsInfo" |
|||
type="primary" |
|||
> |
|||
{{ t("confirm") }} |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div v-if="2 === activeIndex"> |
|||
<div class="setting-item"> |
|||
<h1 class="setting-title">备份</h1> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button @click="exportBackup" type="primary"> 导出 </el-button> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<h1 class="setting-title">还原</h1> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button @click="selectZipfile" type="primary"> 导入 </el-button> |
|||
<input type="file" accept=".zip" style="display: none" ref="zipFileInput" /> |
|||
</div> |
|||
</div> |
|||
<div v-if="0 === activeIndex"> |
|||
<div class="setting-item" style="margin-top: 60px"> |
|||
<label>用户角色</label> |
|||
<el-select v-model="config.userType"> |
|||
<el-option v-for="(item, key) in userTypes" :key="key" :label="item.title" :value="item.value" /> |
|||
</el-select> |
|||
</div> |
|||
<template v-if="config.userType === 'member'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input v-model="config.userInfo.url" placeholder="网址或域名,例子:https://godoos.com 不要加斜杠" /> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>用户名</label> |
|||
<el-input v-model="config.userInfo.username" placeholder="登录用户名" /> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>密码</label> |
|||
<el-input v-model="config.userInfo.password" type="password" placeholder="登录密码" /> |
|||
</div> |
|||
</template> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button @click="saveUserInfo" type="primary"> |
|||
{{ t("confirm") }} |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
<div v-if="3 === activeIndex" class="setting-area"> |
|||
<SetFilePwd v-if="config.userType === 'person'"></SetFilePwd> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div v-if="2 === activeIndex"> |
|||
<div class="setting-item"> |
|||
<h1 class="setting-title">备份</h1> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button |
|||
@click="exportBackup" |
|||
type="primary" |
|||
> |
|||
导出 |
|||
</el-button> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<h1 class="setting-title">还原</h1> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button |
|||
@click="selectZipfile" |
|||
type="primary" |
|||
> |
|||
导入 |
|||
</el-button> |
|||
<input |
|||
type="file" |
|||
accept=".zip" |
|||
style="display: none" |
|||
ref="zipFileInput" |
|||
/> |
|||
</div> |
|||
</div> |
|||
<div v-if="0 === activeIndex"> |
|||
<div |
|||
class="setting-item" |
|||
style="margin-top: 60px" |
|||
> |
|||
<label>用户角色</label> |
|||
<el-select v-model="config.userType"> |
|||
<el-option |
|||
v-for="(item, key) in userTypes" |
|||
:key="key" |
|||
:label="item.title" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</div> |
|||
<template v-if="config.userType === 'member'"> |
|||
<div class="setting-item"> |
|||
<label>服务器地址</label> |
|||
<el-input |
|||
v-model="config.userInfo.url" |
|||
placeholder="网址或域名,例子:https://godoos.com 不要加斜杠" |
|||
/> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>用户名</label> |
|||
<el-input |
|||
v-model="config.userInfo.username" |
|||
placeholder="登录用户名" |
|||
/> |
|||
</div> |
|||
<div class="setting-item"> |
|||
<label>密码</label> |
|||
<el-input |
|||
v-model="config.userInfo.password" |
|||
type="password" |
|||
placeholder="登录密码" |
|||
/> |
|||
</div> |
|||
</template> |
|||
<div class="setting-item"> |
|||
<label></label> |
|||
<el-button |
|||
@click="saveUserInfo" |
|||
type="primary" |
|||
> |
|||
{{ t("confirm") }} |
|||
</el-button> |
|||
</div> |
|||
</div> |
|||
<div |
|||
v-if="3 === activeIndex" |
|||
class="setting-area" |
|||
> |
|||
<SetFilePwd v-if="config.userType === 'person'"></SetFilePwd> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { inject, ref } from "vue"; |
|||
import { Dialog, join, System, t } from "@/system"; |
|||
import JSZip from "jszip"; |
|||
import FileSaver from "file-saver"; |
|||
import { getSystemConfig, setSystemConfig,getClientId } from "@/system/config"; |
|||
import { OpenDirDialog, RestartApp } from "@/util/goutil"; |
|||
import { notifyError, notifySuccess } from "@/util/msg"; |
|||
const config = ref(getSystemConfig()); |
|||
const sys = inject<System>("system")!; |
|||
let zipFile: File | undefined = undefined; |
|||
const zipFileInput = ref(); |
|||
const fileName: any = ref(""); |
|||
const storeList = [ |
|||
// { |
|||
// title: "浏览器存储", |
|||
// value: "browser", |
|||
// }, |
|||
{ |
|||
title: "本地存储", |
|||
value: "local", |
|||
}, |
|||
{ |
|||
title: "远程存储", |
|||
value: "net", |
|||
}, |
|||
{ |
|||
title: "webdav", |
|||
value: "webdav", |
|||
}, |
|||
]; |
|||
import { Dialog, join, System, t } from "@/system"; |
|||
import { |
|||
getClientId, |
|||
getSystemConfig, |
|||
setSystemConfig, |
|||
} from "@/system/config"; |
|||
import { OpenDirDialog, RestartApp } from "@/util/goutil"; |
|||
import { notifyError, notifySuccess } from "@/util/msg"; |
|||
import FileSaver from "file-saver"; |
|||
import JSZip from "jszip"; |
|||
import { inject, ref } from "vue"; |
|||
const config = ref(getSystemConfig()); |
|||
const sys = inject<System>("system")!; |
|||
let zipFile: File | undefined = undefined; |
|||
const zipFileInput = ref(); |
|||
const fileName: any = ref(""); |
|||
const storeList = [ |
|||
// { |
|||
// title: "浏览器存储", |
|||
// value: "browser", |
|||
// }, |
|||
{ |
|||
title: "本地存储", |
|||
value: "local", |
|||
}, |
|||
{ |
|||
title: "远程存储", |
|||
value: "net", |
|||
}, |
|||
{ |
|||
title: "webdav", |
|||
value: "webdav", |
|||
}, |
|||
]; |
|||
|
|||
const items = ["用户角色","个人存储", "备份还原","文件密码箱"]; |
|||
const urlRegex = /^(https?:\/\/)/; |
|||
const userTypes = [ |
|||
{ |
|||
title: "独立用户", |
|||
value: "person", |
|||
}, |
|||
{ |
|||
title: "企业用户", |
|||
value: "member", |
|||
}, |
|||
] |
|||
const activeIndex = ref(0); |
|||
const items = ["用户角色", "个人存储", "备份还原", "文件密码箱"]; |
|||
const urlRegex = /^(https?:\/\/)/; |
|||
const userTypes = [ |
|||
{ |
|||
title: "独立用户", |
|||
value: "person", |
|||
}, |
|||
{ |
|||
title: "企业用户", |
|||
value: "member", |
|||
}, |
|||
]; |
|||
const activeIndex = ref(0); |
|||
|
|||
const selectItem = (index: number) => { |
|||
activeIndex.value = index; |
|||
}; |
|||
function selectFile() { |
|||
OpenDirDialog().then((res: string) => { |
|||
config.value.storePath = res; |
|||
}); |
|||
} |
|||
|
|||
function submitOsInfo() { |
|||
const saveData = toRaw(config.value); |
|||
const postData: any = { |
|||
//name: "osPath", |
|||
type: saveData.storeType, |
|||
}; |
|||
if (saveData.storeType === "browser") { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
return; |
|||
} |
|||
if (saveData.storeType === "local") { |
|||
if (saveData.storePath === "") { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
return; |
|||
} |
|||
postData.name = "osPath"; |
|||
postData.value = saveData.storePath; |
|||
const postUrl = config.value.apiUrl + "/system/setting"; |
|||
fetch(postUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify([postData]), |
|||
}) |
|||
.then((res) => res.json()) |
|||
.then((res) => { |
|||
if (res.code === 0) { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} else { |
|||
Dialog.showMessageBox({ |
|||
message: res.message, |
|||
type: "error", |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
if (saveData.storeType === "net") { |
|||
if (saveData.storenet.url === "") { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
const urlRegex = /^(https?:\/\/)[^\/]+$/; |
|||
if (!urlRegex.test(saveData.storenet.url)) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} |
|||
if (saveData.storeType === "webdav") { |
|||
const urlRegex = /^(https?:\/\/)/; |
|||
if (!urlRegex.test(saveData.webdavClient.url.trim())) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
if (saveData.webdavClient.username === "" || saveData.webdavClient.password === "") { |
|||
Dialog.showMessageBox({ |
|||
message: "用户名或密码不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
postData.name = "webdavClient"; |
|||
postData.value = saveData.webdavClient; |
|||
const postUrl = config.value.apiUrl + "/system/setting"; |
|||
fetch(postUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify([postData]), |
|||
}) |
|||
.then((res) => res.json()) |
|||
.then((res) => { |
|||
if (res.code === 0) { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} else { |
|||
Dialog.showMessageBox({ |
|||
message: res.message, |
|||
type: "error", |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
async function saveUserInfo() { |
|||
const saveData = toRaw(config.value); |
|||
if (saveData.userType == 'person') { |
|||
setSystemConfig(saveData); |
|||
notifySuccess("保存成功"); |
|||
RestartApp(); |
|||
return |
|||
} |
|||
if (!urlRegex.test(saveData.userInfo.url.trim())) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
if (saveData.userInfo.username === "" || saveData.userInfo.password === "") { |
|||
Dialog.showMessageBox({ |
|||
message: "用户名或密码不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
const password = saveData.userInfo.password |
|||
const serverUrl = saveData.userInfo.url + '/member/login' |
|||
const res = await fetch(serverUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
username: saveData.userInfo.username, |
|||
password: password, |
|||
clientId: getClientId(), |
|||
}), |
|||
}); |
|||
if (res.status === 200) { |
|||
const data = await res.json(); |
|||
if (data.success) { |
|||
data.data.url = saveData.userInfo.url |
|||
data.data.password = password |
|||
saveData.userInfo = data.data |
|||
setSystemConfig(saveData); |
|||
notifySuccess("登录成功"); |
|||
RestartApp(); |
|||
return |
|||
}else{ |
|||
notifyError(data.message) |
|||
} |
|||
}else{ |
|||
notifyError("登录失败"); |
|||
} |
|||
return |
|||
} |
|||
async function exportBackup() { |
|||
const { setProgress } = Dialog.showProcessDialog({ |
|||
message: `正在打包`, |
|||
}); |
|||
try { |
|||
const zip = new JSZip(); |
|||
await dfsPackage("/", zip, setProgress); |
|||
zip.generateAsync({ type: "blob" }).then(function (content) { |
|||
FileSaver.saveAs(content, "backup.zip"); |
|||
setProgress(100); |
|||
}); |
|||
} catch (error) { |
|||
//console.log(error); |
|||
Dialog.showMessageBox({ |
|||
message: "打包失败", |
|||
type: "error", |
|||
}).finally(() => { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
setProgress(100); |
|||
}); |
|||
} |
|||
} |
|||
async function dfsPackage(path: string, zip: JSZip, setProgress: any) { |
|||
const dir = await sys.fs.readdir(path); |
|||
|
|||
for (let i = 0; i < dir.length; i++) { |
|||
const item = dir[i]; |
|||
const stat = await sys.fs.stat(item.path); |
|||
setProgress(Math.max((i / dir.length) * 100 - 0.1, 0.1)); |
|||
if (stat) { |
|||
if (stat.isDirectory) { |
|||
await dfsPackage(item.path, zip, setProgress); |
|||
} else { |
|||
const content = await sys.fs.readFile(item.path); |
|||
try { |
|||
atob(content || ""); |
|||
zip.file(item.path, content || ""); |
|||
} catch (error) { |
|||
zip.file(item.path, content || ""); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
function selectZipfile() { |
|||
zipFileInput.value.click(); |
|||
zipFileInput.value.onchange = (e: any) => { |
|||
const tar: any = e.target as HTMLInputElement; |
|||
if (tar.files?.[0]) { |
|||
zipFile = tar.files[0]; |
|||
fileName.value = zipFile?.name; |
|||
importBackup(); |
|||
} else { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
} |
|||
}; |
|||
} |
|||
async function importBackup(path = "") { |
|||
if (!zipFile) { |
|||
return; |
|||
} |
|||
const { setProgress } = Dialog.showProcessDialog({ |
|||
message: "正在恢复备份", |
|||
}); |
|||
try { |
|||
const unziped = await JSZip.loadAsync(zipFile); |
|||
const unzipArray: Array<JSZip.JSZipObject> = []; |
|||
unziped.forEach((_, zipEntry) => { |
|||
unzipArray.push(zipEntry); |
|||
}); |
|||
for (let i = 0; i < unzipArray.length; i++) { |
|||
const zipEntry = unzipArray[i]; |
|||
setProgress((i / unzipArray.length) * 100); |
|||
if (zipEntry.dir) { |
|||
await sys.fs.mkdir(join(path, zipEntry.name)); |
|||
} else { |
|||
let fileC: any = await zipEntry.async("string"); |
|||
//console.log(fileC); |
|||
if (!fileC.startsWith("link::")) { |
|||
fileC = await zipEntry.async("arraybuffer"); |
|||
} |
|||
sys.fs.writeFile(join(path, zipEntry.name), fileC); |
|||
} |
|||
} |
|||
setProgress(100); |
|||
selectItem(1); |
|||
} catch (e) { |
|||
//console.log(e); |
|||
setTimeout(() => { |
|||
Dialog.showMessageBox({ |
|||
message: "恢复失败", |
|||
type: "error", |
|||
}).finally(() => { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
setProgress(100); |
|||
}); |
|||
}, 100); |
|||
} |
|||
} |
|||
const selectItem = (index: number) => { |
|||
activeIndex.value = index; |
|||
}; |
|||
function selectFile() { |
|||
OpenDirDialog().then((res: string) => { |
|||
config.value.storePath = res; |
|||
}); |
|||
} |
|||
|
|||
function submitOsInfo() { |
|||
const saveData = toRaw(config.value); |
|||
const postData: any = { |
|||
//name: "osPath", |
|||
type: saveData.storeType, |
|||
}; |
|||
if (saveData.storeType === "browser") { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
return; |
|||
} |
|||
if (saveData.storeType === "local") { |
|||
if (saveData.storePath === "") { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
return; |
|||
} |
|||
postData.name = "osPath"; |
|||
postData.value = saveData.storePath; |
|||
const postUrl = config.value.apiUrl + "/system/setting"; |
|||
fetch(postUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify([postData]), |
|||
}) |
|||
.then((res) => res.json()) |
|||
.then((res) => { |
|||
if (res.code === 0) { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} else { |
|||
Dialog.showMessageBox({ |
|||
message: res.message, |
|||
type: "error", |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
if (saveData.storeType === "net") { |
|||
if (saveData.storenet.url === "") { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
const urlRegex = /^(https?:\/\/)[^\/]+$/; |
|||
if (!urlRegex.test(saveData.storenet.url)) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} |
|||
if (saveData.storeType === "webdav") { |
|||
const urlRegex = /^(https?:\/\/)/; |
|||
if (!urlRegex.test(saveData.webdavClient.url.trim())) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
if ( |
|||
saveData.webdavClient.username === "" || |
|||
saveData.webdavClient.password === "" |
|||
) { |
|||
Dialog.showMessageBox({ |
|||
message: "用户名或密码不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
postData.name = "webdavClient"; |
|||
postData.value = saveData.webdavClient; |
|||
const postUrl = config.value.apiUrl + "/system/setting"; |
|||
fetch(postUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify([postData]), |
|||
}) |
|||
.then((res) => res.json()) |
|||
.then((res) => { |
|||
if (res.code === 0) { |
|||
setSystemConfig(saveData); |
|||
RestartApp(); |
|||
} else { |
|||
Dialog.showMessageBox({ |
|||
message: res.message, |
|||
type: "error", |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
async function saveUserInfo() { |
|||
const saveData = toRaw(config.value); |
|||
if (saveData.userType == "person") { |
|||
setSystemConfig(saveData); |
|||
notifySuccess("保存成功"); |
|||
RestartApp(); |
|||
return; |
|||
} |
|||
if (!urlRegex.test(saveData.userInfo.url.trim())) { |
|||
Dialog.showMessageBox({ |
|||
message: "服务器地址格式错误", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
if ( |
|||
saveData.userInfo.username === "" || |
|||
saveData.userInfo.password === "" |
|||
) { |
|||
Dialog.showMessageBox({ |
|||
message: "用户名或密码不能为空", |
|||
type: "error", |
|||
}); |
|||
return; |
|||
} |
|||
const password = saveData.userInfo.password; |
|||
const serverUrl = saveData.userInfo.url + "/member/login"; |
|||
const res = await fetch(serverUrl, { |
|||
method: "POST", |
|||
body: JSON.stringify({ |
|||
username: saveData.userInfo.username, |
|||
password: password, |
|||
clientId: getClientId(), |
|||
}), |
|||
}); |
|||
if (res.status === 200) { |
|||
const data = await res.json(); |
|||
if (data.success) { |
|||
data.data.url = saveData.userInfo.url; |
|||
data.data.password = password; |
|||
saveData.userInfo = data.data; |
|||
setSystemConfig(saveData); |
|||
notifySuccess("登录成功"); |
|||
RestartApp(); |
|||
return; |
|||
} else { |
|||
notifyError(data.message); |
|||
} |
|||
} else { |
|||
notifyError("登录失败"); |
|||
} |
|||
return; |
|||
} |
|||
async function exportBackup() { |
|||
const { setProgress } = Dialog.showProcessDialog({ |
|||
message: `正在打包`, |
|||
}); |
|||
try { |
|||
const zip = new JSZip(); |
|||
await dfsPackage("/", zip, setProgress); |
|||
zip.generateAsync({ type: "blob" }).then(function (content) { |
|||
FileSaver.saveAs(content, "backup.zip"); |
|||
setProgress(100); |
|||
}); |
|||
} catch (error) { |
|||
//console.log(error); |
|||
Dialog.showMessageBox({ |
|||
message: "打包失败", |
|||
type: "error", |
|||
}).finally(() => { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
setProgress(100); |
|||
}); |
|||
} |
|||
} |
|||
async function dfsPackage(path: string, zip: JSZip, setProgress: any) { |
|||
const dir = await sys.fs.readdir(path); |
|||
|
|||
for (let i = 0; i < dir.length; i++) { |
|||
const item = dir[i]; |
|||
const stat = await sys.fs.stat(item.path); |
|||
setProgress(Math.max((i / dir.length) * 100 - 0.1, 0.1)); |
|||
if (stat) { |
|||
if (stat.isDirectory) { |
|||
await dfsPackage(item.path, zip, setProgress); |
|||
} else { |
|||
const content = await sys.fs.readFile(item.path); |
|||
try { |
|||
atob(content || ""); |
|||
zip.file(item.path, content || ""); |
|||
} catch (error) { |
|||
zip.file(item.path, content || ""); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
function selectZipfile() { |
|||
zipFileInput.value.click(); |
|||
zipFileInput.value.onchange = (e: any) => { |
|||
const tar: any = e.target as HTMLInputElement; |
|||
if (tar.files?.[0]) { |
|||
zipFile = tar.files[0]; |
|||
fileName.value = zipFile?.name; |
|||
importBackup(); |
|||
} else { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
} |
|||
}; |
|||
} |
|||
async function importBackup(path = "") { |
|||
if (!zipFile) { |
|||
return; |
|||
} |
|||
const { setProgress } = Dialog.showProcessDialog({ |
|||
message: "正在恢复备份", |
|||
}); |
|||
try { |
|||
const unziped = await JSZip.loadAsync(zipFile); |
|||
const unzipArray: Array<JSZip.JSZipObject> = []; |
|||
unziped.forEach((_, zipEntry) => { |
|||
unzipArray.push(zipEntry); |
|||
}); |
|||
for (let i = 0; i < unzipArray.length; i++) { |
|||
const zipEntry = unzipArray[i]; |
|||
setProgress((i / unzipArray.length) * 100); |
|||
if (zipEntry.dir) { |
|||
await sys.fs.mkdir(join(path, zipEntry.name)); |
|||
} else { |
|||
let fileC: any = await zipEntry.async("string"); |
|||
//console.log(fileC); |
|||
if (!fileC.startsWith("link::")) { |
|||
fileC = await zipEntry.async("arraybuffer"); |
|||
} |
|||
sys.fs.writeFile(join(path, zipEntry.name), fileC); |
|||
} |
|||
} |
|||
setProgress(100); |
|||
selectItem(1); |
|||
} catch (e) { |
|||
//console.log(e); |
|||
setTimeout(() => { |
|||
Dialog.showMessageBox({ |
|||
message: "恢复失败", |
|||
type: "error", |
|||
}).finally(() => { |
|||
zipFile = undefined; |
|||
fileName.value = ""; |
|||
setProgress(100); |
|||
}); |
|||
}, 100); |
|||
} |
|||
} |
|||
</script> |
|||
<style scoped> |
|||
@import "./setStyle.css"; |
|||
@import "./setStyle.css"; |
|||
|
|||
.ctrl { |
|||
width: 100px; |
|||
} |
|||
.ctrl { |
|||
width: 100px; |
|||
} |
|||
|
|||
.setting-item { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.setting-area { |
|||
width: 100%; |
|||
height: 90%; |
|||
} |
|||
.setting-item { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.setting-area { |
|||
width: 100%; |
|||
height: 90%; |
|||
} |
|||
</style> |
|||
|
@ -0,0 +1,12 @@ |
|||
import { defineStore } from 'pinia'; |
|||
|
|||
export const useLoginStore = defineStore("login", () => { |
|||
// 第三方登录方式
|
|||
const ThirdPartyPlatform = ref<string | null>(null); |
|||
|
|||
const State = ref<string | null>(null); |
|||
return { |
|||
ThirdPartyPlatform, |
|||
State |
|||
}; |
|||
}); |
Loading…
Reference in new issue