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> |
<template> |
||||
<div class="lockscreen" :class="lockClassName"> |
<div |
||||
<el-card class="login-box" shadow="never"> |
class="lockscreen" |
||||
<div class="avatar-container"> |
:class="lockClassName" |
||||
<el-avatar size="large"> |
> |
||||
<img src="/logo.png" alt="Logo" /> |
<el-card |
||||
</el-avatar> |
class="login-box" |
||||
</div> |
shadow="never" |
||||
<el-form v-if="!isRegisterMode" label-position="left" label-width="0px"> |
> |
||||
<el-form-item> |
<div class="avatar-container"> |
||||
<el-input |
<el-avatar size="large"> |
||||
v-model="userName" |
<img |
||||
placeholder="请输入用户名" |
src="/logo.png" |
||||
autofocus |
alt="Logo" |
||||
prefix-icon="UserFilled" |
/> |
||||
></el-input> |
</el-avatar> |
||||
</el-form-item> |
</div> |
||||
<el-form-item v-if="!sys._options.noPassword"> |
<el-form |
||||
<el-input |
v-if="!isRegisterMode" |
||||
v-model="userPassword" |
label-position="left" |
||||
type="password" |
label-width="0px" |
||||
placeholder="请输入登录密码" |
> |
||||
show-password |
<el-form-item> |
||||
prefix-icon="Key" |
<el-input |
||||
@keyup.enter="onLogin" |
v-model="userName" |
||||
></el-input> |
placeholder="请输入用户名" |
||||
</el-form-item> |
autofocus |
||||
<el-button type="primary" @click="onLogin">登录</el-button> |
prefix-icon="UserFilled" |
||||
<div class="actions" v-if="config.userType === 'member'"> |
></el-input> |
||||
<a href="#" @click.prevent="toggleRegister">注册新用户</a> |
</el-form-item> |
||||
<a href="#" @click.prevent="toggleUserSwitch">切换角色</a> |
<el-form-item v-if="!sys._options.noPassword"> |
||||
</div> |
<el-input |
||||
</el-form> |
v-model="userPassword" |
||||
<el-form v-else label-position="left" label-width="0px" :model="regForm" ref="regFormRef" :rules="rules"> |
type="password" |
||||
<el-form-item prop="username"> |
placeholder="请输入登录密码" |
||||
<el-input |
show-password |
||||
v-model="regForm.username" |
prefix-icon="Key" |
||||
placeholder="请输入用户名" |
@keyup.enter="onLogin" |
||||
prefix-icon="UserFilled" |
></el-input> |
||||
></el-input> |
</el-form-item> |
||||
</el-form-item> |
<el-button |
||||
<el-form-item prop="nickname"> |
type="primary" |
||||
<el-input |
@click="onLogin" |
||||
v-model="regForm.nickname" |
>登录</el-button |
||||
placeholder="请输入真实姓名" |
> |
||||
prefix-icon="Avatar" |
<div class="divider"> |
||||
></el-input> |
<span>第三方登录</span> |
||||
</el-form-item> |
</div> |
||||
<el-form-item prop="email"> |
<div class="third-party-login"> |
||||
<el-input |
<img |
||||
v-model="regForm.email" |
style=" |
||||
placeholder="请输入邮箱" |
width: 25px; |
||||
prefix-icon="Message" |
height: 25px; |
||||
></el-input> |
cursor: pointer; |
||||
</el-form-item> |
color: #409eff; |
||||
<el-form-item prop="phone"> |
" |
||||
<el-input |
src="../../../public/image/login/微信.png" |
||||
v-model="regForm.phone" |
@click="onThirdPartyLogin('wechat')" |
||||
placeholder="请输入手机号" |
/> |
||||
prefix-icon="Iphone" |
<img |
||||
></el-input> |
style=" |
||||
</el-form-item> |
width: 25px; |
||||
<el-form-item prop="password"> |
height: 25px; |
||||
<el-input |
cursor: pointer; |
||||
v-model="regForm.password" |
color: #409eff; |
||||
type="password" |
" |
||||
placeholder="请输入密码" |
src="../../../public/image/login/QQ.png" |
||||
show-password |
@click="onThirdPartyLogin('qq')" |
||||
prefix-icon="Key" |
/> |
||||
></el-input> |
<img |
||||
</el-form-item> |
style=" |
||||
<el-form-item prop="confirmPassword"> |
width: 25px; |
||||
<el-input |
height: 25px; |
||||
v-model="regForm.confirmPassword" |
cursor: pointer; |
||||
type="password" |
color: #409eff; |
||||
placeholder="请再次输入密码" |
" |
||||
show-password |
src="../../../public/image/login/新浪微博.png" |
||||
prefix-icon="Lock" |
@click="onThirdPartyLogin('sina')" |
||||
></el-input> |
/> |
||||
</el-form-item> |
<img |
||||
<el-button type="primary" @click="onRegister">注册</el-button> |
style=" |
||||
<div class="actions"> |
width: 25px; |
||||
<a href="#" @click.prevent="toggleRegister">返回登录</a> |
height: 25px; |
||||
</div> |
cursor: pointer; |
||||
</el-form> |
color: #409eff; |
||||
</el-card> |
" |
||||
</div> |
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> |
</template> |
||||
|
|
||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||
import { ref, onMounted } from 'vue'; |
import { useLoginStore } from "@/stores/login"; |
||||
import { useSystem } from '@/system'; |
import { useSystem } from "@/system"; |
||||
import { getSystemConfig, setSystemConfig } from '@/system/config'; |
import { getSystemConfig, setSystemConfig } from "@/system/config"; |
||||
import { notifyError } from '@/util/msg'; |
import router from "@/system/router"; |
||||
import { RestartApp } from '@/util/goutil'; |
import { RestartApp } from "@/util/goutil"; |
||||
|
import { notifyError } from "@/util/msg"; |
||||
const sys = useSystem(); |
import { onMounted, ref } from "vue"; |
||||
const loginCallback = sys._options.loginCallback; |
const store = useLoginStore(); |
||||
const config = getSystemConfig(); |
const sys = useSystem(); |
||||
const lockClassName = ref('screen-show'); |
const loginCallback = sys._options.loginCallback; |
||||
const isRegisterMode = ref(false); |
const config = getSystemConfig(); |
||||
|
const lockClassName = ref("screen-show"); |
||||
function loginSuccess() { |
const isRegisterMode = ref(false); |
||||
lockClassName.value = 'screen-hidean'; |
function loginSuccess() { |
||||
setTimeout(() => { |
lockClassName.value = "screen-hidean"; |
||||
lockClassName.value = 'screen-hide'; |
setTimeout(() => { |
||||
}, 500); |
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); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
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> |
</script> |
||||
|
|
||||
<style scoped lang="scss"> |
<style scoped lang="scss"> |
||||
.lockscreen { |
.lockscreen { |
||||
position: absolute; |
position: absolute; |
||||
top: 0; |
top: 0; |
||||
right: 0; |
right: 0; |
||||
bottom: 0; |
bottom: 0; |
||||
left: 0; |
left: 0; |
||||
z-index: 201; |
z-index: 201; |
||||
display: flex; |
display: flex; |
||||
justify-content: center; |
justify-content: center; |
||||
align-items: center; |
align-items: center; |
||||
overflow: hidden; |
overflow: hidden; |
||||
color: #fff; |
color: #fff; |
||||
background-color: rgba(25, 28, 34, 0.78); |
background-color: rgba(25, 28, 34, 0.78); |
||||
backdrop-filter: blur(7px); |
backdrop-filter: blur(7px); |
||||
|
|
||||
.login-box{ |
.login-box { |
||||
width: 300px; |
width: 300px; |
||||
padding: 20px; |
padding: 20px; |
||||
text-align: center; |
text-align: center; |
||||
background: #ffffff; |
background: #ffffff; |
||||
border-radius: 10px; |
border-radius: 10px; |
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); |
||||
border: 1px solid #e0e0e0; |
border: 1px solid #e0e0e0; |
||||
|
|
||||
.avatar-container { |
.avatar-container { |
||||
margin-bottom: 20px; |
margin-bottom: 20px; |
||||
} |
} |
||||
|
|
||||
.el-input { |
.el-input { |
||||
width: 100%; |
width: 100%; |
||||
margin-bottom: 10px; |
margin-bottom: 10px; |
||||
background: #f9f9f9; |
background: #f9f9f9; |
||||
border-radius: 4px; |
border-radius: 4px; |
||||
} |
} |
||||
|
|
||||
.el-button { |
.el-button { |
||||
width: 100%; |
width: 100%; |
||||
margin-top: 10px; |
margin-top: 10px; |
||||
background: #409eff; |
background: #409eff; |
||||
color: #ffffff; |
color: #ffffff; |
||||
border: none; |
border: none; |
||||
border-radius: 4px; |
border-radius: 4px; |
||||
transition: background 0.3s ease; |
transition: background 0.3s ease; |
||||
} |
} |
||||
|
|
||||
.el-button:hover { |
.el-button:hover { |
||||
background: #66b1ff; |
background: #66b1ff; |
||||
} |
} |
||||
|
|
||||
.tip { |
.tip { |
||||
padding: 4px 0; |
padding: 4px 0; |
||||
font-size: 12px; |
font-size: 12px; |
||||
color: red; |
color: red; |
||||
height: 30px; |
height: 30px; |
||||
} |
} |
||||
|
|
||||
.actions { |
.actions { |
||||
margin-top: 10px; |
margin-top: 10px; |
||||
display: flex; |
display: flex; |
||||
justify-content: space-between; |
justify-content: space-between; |
||||
|
|
||||
a { |
a { |
||||
color: #409eff; |
color: #409eff; |
||||
text-decoration: none; |
text-decoration: none; |
||||
cursor: pointer; |
cursor: pointer; |
||||
|
|
||||
&:hover { |
&:hover { |
||||
text-decoration: underline; |
text-decoration: underline; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
.screen-hidean { |
.screen-hidean { |
||||
animation: outan 0.5s forwards; |
animation: outan 0.5s forwards; |
||||
} |
} |
||||
|
|
||||
.screen-hide { |
.screen-hide { |
||||
display: none; |
display: none; |
||||
} |
} |
||||
} |
|
||||
|
.third-party-login { |
||||
@keyframes outan { |
margin-top: 10px; |
||||
0% { |
display: flex; |
||||
opacity: 1; |
justify-content: center; |
||||
} |
gap: 15px; |
||||
|
|
||||
30% { |
el-icon { |
||||
opacity: 0; |
font-size: 24px; |
||||
} |
cursor: pointer; |
||||
|
transition: color 0.3s ease; |
||||
100% { |
|
||||
transform: translateY(-100%); |
&:hover { |
||||
opacity: 0; |
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> |
</style> |
@ -1,422 +1,500 @@ |
|||||
<template> |
<template> |
||||
<div class="container"> |
<div class="container"> |
||||
<div class="nav"> |
<div class="nav"> |
||||
<ul> |
<ul> |
||||
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)" |
<li |
||||
:class="{ active: index === activeIndex }"> |
v-for="(item, index) in items" |
||||
{{ item }} |
:key="index" |
||||
</li> |
@click="selectItem(index)" |
||||
</ul> |
:class="{ active: index === activeIndex }" |
||||
</div> |
> |
||||
<div class="setting"> |
{{ item }} |
||||
<div v-if="1 === activeIndex"> |
</li> |
||||
<div class="setting-item" style="margin-top: 60px"> |
</ul> |
||||
<label>存储方式</label> |
</div> |
||||
<el-select v-model="config.storeType"> |
<div class="setting"> |
||||
<el-option v-for="(item, key) in storeList" :key="key" :label="item.title" :value="item.value" /> |
<div v-if="1 === activeIndex"> |
||||
</el-select> |
<div |
||||
</div> |
class="setting-item" |
||||
<div class="setting-item" v-if="config.storeType === 'local'"> |
style="margin-top: 60px" |
||||
<label>存储地址</label> |
> |
||||
<el-input v-model="config.storePath" @click="selectFile()" placeholder="可为空,为空则取系统默认存储地址" /> |
<label>存储方式</label> |
||||
</div> |
<el-select v-model="config.storeType"> |
||||
<template v-if="config.storeType === 'net'"> |
<el-option |
||||
<div class="setting-item"> |
v-for="(item, key) in storeList" |
||||
<label>服务器地址</label> |
:key="key" |
||||
<el-input v-model="config.storenet.url" placeholder="http://192.168.1.16:56780 不要加斜杠" /> |
:label="item.title" |
||||
</div> |
:value="item.value" |
||||
</template> |
/> |
||||
<template v-if="config.storeType === 'webdav'"> |
</el-select> |
||||
<div class="setting-item"> |
</div> |
||||
<label>服务器地址</label> |
<div |
||||
<el-input v-model="config.webdavClient.url" placeholder="https://godoos.com/webdav 不要加斜杠" /> |
class="setting-item" |
||||
</div> |
v-if="config.storeType === 'local'" |
||||
<div class="setting-item"> |
> |
||||
<label>登陆用户名</label> |
<label>存储地址</label> |
||||
<el-input v-model="config.webdavClient.username" /> |
<el-input |
||||
</div> |
v-model="config.storePath" |
||||
<div class="setting-item"> |
@click="selectFile()" |
||||
<label>登陆密码</label> |
placeholder="可为空,为空则取系统默认存储地址" |
||||
<el-input v-model="config.webdavClient.password" type="password" /> |
/> |
||||
</div> |
</div> |
||||
</template> |
<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"> |
<div class="setting-item"> |
||||
<label></label> |
<label></label> |
||||
<el-button @click="submitOsInfo" type="primary"> |
<el-button |
||||
{{ t("confirm") }} |
@click="submitOsInfo" |
||||
</el-button> |
type="primary" |
||||
</div> |
> |
||||
</div> |
{{ t("confirm") }} |
||||
|
</el-button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
<div v-if="2 === activeIndex"> |
<div v-if="2 === activeIndex"> |
||||
<div class="setting-item"> |
<div class="setting-item"> |
||||
<h1 class="setting-title">备份</h1> |
<h1 class="setting-title">备份</h1> |
||||
</div> |
</div> |
||||
<div class="setting-item"> |
<div class="setting-item"> |
||||
<label></label> |
<label></label> |
||||
<el-button @click="exportBackup" type="primary"> 导出 </el-button> |
<el-button |
||||
</div> |
@click="exportBackup" |
||||
<div class="setting-item"> |
type="primary" |
||||
<h1 class="setting-title">还原</h1> |
> |
||||
</div> |
导出 |
||||
<div class="setting-item"> |
</el-button> |
||||
<label></label> |
</div> |
||||
<el-button @click="selectZipfile" type="primary"> 导入 </el-button> |
<div class="setting-item"> |
||||
<input type="file" accept=".zip" style="display: none" ref="zipFileInput" /> |
<h1 class="setting-title">还原</h1> |
||||
</div> |
</div> |
||||
</div> |
<div class="setting-item"> |
||||
<div v-if="0 === activeIndex"> |
<label></label> |
||||
<div class="setting-item" style="margin-top: 60px"> |
<el-button |
||||
<label>用户角色</label> |
@click="selectZipfile" |
||||
<el-select v-model="config.userType"> |
type="primary" |
||||
<el-option v-for="(item, key) in userTypes" :key="key" :label="item.title" :value="item.value" /> |
> |
||||
</el-select> |
导入 |
||||
</div> |
</el-button> |
||||
<template v-if="config.userType === 'member'"> |
<input |
||||
<div class="setting-item"> |
type="file" |
||||
<label>服务器地址</label> |
accept=".zip" |
||||
<el-input v-model="config.userInfo.url" placeholder="网址或域名,例子:https://godoos.com 不要加斜杠" /> |
style="display: none" |
||||
</div> |
ref="zipFileInput" |
||||
<div class="setting-item"> |
/> |
||||
<label>用户名</label> |
</div> |
||||
<el-input v-model="config.userInfo.username" placeholder="登录用户名" /> |
</div> |
||||
</div> |
<div v-if="0 === activeIndex"> |
||||
<div class="setting-item"> |
<div |
||||
<label>密码</label> |
class="setting-item" |
||||
<el-input v-model="config.userInfo.password" type="password" placeholder="登录密码" /> |
style="margin-top: 60px" |
||||
</div> |
> |
||||
</template> |
<label>用户角色</label> |
||||
<div class="setting-item"> |
<el-select v-model="config.userType"> |
||||
<label></label> |
<el-option |
||||
<el-button @click="saveUserInfo" type="primary"> |
v-for="(item, key) in userTypes" |
||||
{{ t("confirm") }} |
:key="key" |
||||
</el-button> |
:label="item.title" |
||||
</div> |
:value="item.value" |
||||
</div> |
/> |
||||
<div v-if="3 === activeIndex" class="setting-area"> |
</el-select> |
||||
<SetFilePwd v-if="config.userType === 'person'"></SetFilePwd> |
</div> |
||||
</div> |
<template v-if="config.userType === 'member'"> |
||||
</div> |
<div class="setting-item"> |
||||
</div> |
<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> |
</template> |
||||
|
|
||||
<script lang="ts" setup> |
<script lang="ts" setup> |
||||
import { inject, ref } from "vue"; |
import { Dialog, join, System, t } from "@/system"; |
||||
import { Dialog, join, System, t } from "@/system"; |
import { |
||||
import JSZip from "jszip"; |
getClientId, |
||||
import FileSaver from "file-saver"; |
getSystemConfig, |
||||
import { getSystemConfig, setSystemConfig,getClientId } from "@/system/config"; |
setSystemConfig, |
||||
import { OpenDirDialog, RestartApp } from "@/util/goutil"; |
} from "@/system/config"; |
||||
import { notifyError, notifySuccess } from "@/util/msg"; |
import { OpenDirDialog, RestartApp } from "@/util/goutil"; |
||||
const config = ref(getSystemConfig()); |
import { notifyError, notifySuccess } from "@/util/msg"; |
||||
const sys = inject<System>("system")!; |
import FileSaver from "file-saver"; |
||||
let zipFile: File | undefined = undefined; |
import JSZip from "jszip"; |
||||
const zipFileInput = ref(); |
import { inject, ref } from "vue"; |
||||
const fileName: any = ref(""); |
const config = ref(getSystemConfig()); |
||||
const storeList = [ |
const sys = inject<System>("system")!; |
||||
// { |
let zipFile: File | undefined = undefined; |
||||
// title: "浏览器存储", |
const zipFileInput = ref(); |
||||
// value: "browser", |
const fileName: any = ref(""); |
||||
// }, |
const storeList = [ |
||||
{ |
// { |
||||
title: "本地存储", |
// title: "浏览器存储", |
||||
value: "local", |
// value: "browser", |
||||
}, |
// }, |
||||
{ |
{ |
||||
title: "远程存储", |
title: "本地存储", |
||||
value: "net", |
value: "local", |
||||
}, |
}, |
||||
{ |
{ |
||||
title: "webdav", |
title: "远程存储", |
||||
value: "webdav", |
value: "net", |
||||
}, |
}, |
||||
]; |
{ |
||||
|
title: "webdav", |
||||
|
value: "webdav", |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
const items = ["用户角色","个人存储", "备份还原","文件密码箱"]; |
const items = ["用户角色", "个人存储", "备份还原", "文件密码箱"]; |
||||
const urlRegex = /^(https?:\/\/)/; |
const urlRegex = /^(https?:\/\/)/; |
||||
const userTypes = [ |
const userTypes = [ |
||||
{ |
{ |
||||
title: "独立用户", |
title: "独立用户", |
||||
value: "person", |
value: "person", |
||||
}, |
}, |
||||
{ |
{ |
||||
title: "企业用户", |
title: "企业用户", |
||||
value: "member", |
value: "member", |
||||
}, |
}, |
||||
] |
]; |
||||
const activeIndex = ref(0); |
const activeIndex = ref(0); |
||||
|
|
||||
const selectItem = (index: number) => { |
const selectItem = (index: number) => { |
||||
activeIndex.value = index; |
activeIndex.value = index; |
||||
}; |
}; |
||||
function selectFile() { |
function selectFile() { |
||||
OpenDirDialog().then((res: string) => { |
OpenDirDialog().then((res: string) => { |
||||
config.value.storePath = res; |
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); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
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> |
</script> |
||||
<style scoped> |
<style scoped> |
||||
@import "./setStyle.css"; |
@import "./setStyle.css"; |
||||
|
|
||||
.ctrl { |
.ctrl { |
||||
width: 100px; |
width: 100px; |
||||
} |
} |
||||
|
|
||||
.setting-item { |
.setting-item { |
||||
display: flex; |
display: flex; |
||||
align-items: center; |
align-items: center; |
||||
} |
} |
||||
.setting-area { |
.setting-area { |
||||
width: 100%; |
width: 100%; |
||||
height: 90%; |
height: 90%; |
||||
} |
} |
||||
</style> |
</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