Browse Source

Merge branch 'master' of https://gitee.com/godoos/godoos

master
godo 6 months ago
parent
commit
cc6c8cbac9
  1. 2
      frontend/components.d.ts
  2. 6513
      frontend/pnpm-lock.yaml
  3. BIN
      frontend/public/image/login/GitHub.png
  4. BIN
      frontend/public/image/login/QQ.png
  5. BIN
      frontend/public/image/login/gitee.png
  6. BIN
      frontend/public/image/login/微信.png
  7. BIN
      frontend/public/image/login/新浪微博.png
  8. 891
      frontend/src/components/desktop/LockDesktop.vue
  9. 892
      frontend/src/components/setting/SetSystem.vue
  10. 12
      frontend/src/stores/login.ts
  11. 22
      frontend/src/system/index.ts
  12. 9
      frontend/src/system/router.ts
  13. 11
      frontend/src/system/type/type.ts

2
frontend/components.d.ts

@ -68,6 +68,7 @@ declare module 'vue' {
ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarousel: typeof import('element-plus/es')['ElCarousel']
ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem'] ElCarouselItem: typeof import('element-plus/es')['ElCarouselItem']
ElCol: typeof import('element-plus/es')['ElCol'] ElCol: typeof import('element-plus/es')['ElCol']
ElColorPicker: typeof import('element-plus/es')['ElColorPicker']
ElContainer: typeof import('element-plus/es')['ElContainer'] ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog'] ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElDrawer: typeof import('element-plus/es')['ElDrawer']
@ -105,6 +106,7 @@ declare module 'vue' {
FilePwd: typeof import('./src/components/oa/FilePwd.vue')['default'] FilePwd: typeof import('./src/components/oa/FilePwd.vue')['default']
FileTree: typeof import('./src/components/builtin/FileTree.vue')['default'] FileTree: typeof import('./src/components/builtin/FileTree.vue')['default']
FileViewer: typeof import('./src/components/builtin/FileViewer.vue')['default'] FileViewer: typeof import('./src/components/builtin/FileViewer.vue')['default']
GithubNext: typeof import('./src/components/desktop/GithubNext.vue')['default']
IframeFile: typeof import('./src/components/window/IframeFile.vue')['default'] IframeFile: typeof import('./src/components/window/IframeFile.vue')['default']
ImageViewer: typeof import('./src/components/builtin/ImageViewer.vue')['default'] ImageViewer: typeof import('./src/components/builtin/ImageViewer.vue')['default']
Install: typeof import('./src/components/install/Install.vue')['default'] Install: typeof import('./src/components/install/Install.vue')['default']

6513
frontend/pnpm-lock.yaml

File diff suppressed because it is too large

BIN
frontend/public/image/login/GitHub.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
frontend/public/image/login/QQ.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
frontend/public/image/login/gitee.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
frontend/public/image/login/微信.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
frontend/public/image/login/新浪微博.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

891
frontend/src/components/desktop/LockDesktop.vue

@ -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> => {
// stateCSRF,使
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>

892
frontend/src/components/setting/SetSystem.vue

@ -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>

12
frontend/src/stores/login.ts

@ -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
};
});

22
frontend/src/system/index.ts

@ -14,9 +14,9 @@ import {
} from './type/type'; } from './type/type';
import { BrowserWindow, BrowserWindowOption } from './window/BrowserWindow'; import { BrowserWindow, BrowserWindowOption } from './window/BrowserWindow';
import { useUpgradeStore } from '@/stores/upgrade';
import { useAssistantStore } from '@/stores/assistant'; import { useAssistantStore } from '@/stores/assistant';
import { useModelStore } from "@/stores/model.ts"; import { useModelStore } from "@/stores/model.ts";
import { useUpgradeStore } from '@/stores/upgrade';
import { RestartApp } from '@/util/goutil'; import { RestartApp } from '@/util/goutil';
import { notifyError } from '@/util/msg'; import { notifyError } from '@/util/msg';
import { isShareFile } from '@/util/sharePath'; import { isShareFile } from '@/util/sharePath';
@ -134,6 +134,7 @@ export class System {
*/ */
private async isLogin() { private async isLogin() {
const config = getSystemConfig(); const config = getSystemConfig();
if (config.userType == 'person') { if (config.userType == 'person') {
if (!this._options.login) { if (!this._options.login) {
this._rootState.state = SystemStateEnum.open; this._rootState.state = SystemStateEnum.open;
@ -159,6 +160,7 @@ export class System {
}; };
} }
} else { } else {
// 非个人用户
const userInfo = config.userInfo; const userInfo = config.userInfo;
if (userInfo.url == '') { if (userInfo.url == '') {
return true; return true;
@ -171,25 +173,35 @@ export class System {
const data = await res.json(); const data = await res.json();
//console.log(data) //console.log(data)
if (data.success) { if (data.success) {
// 登录成功
this._rootState.state = SystemStateEnum.open; this._rootState.state = SystemStateEnum.open;
return true; return true;
} else { } else {
// 登录失败
//return true; //return true;
this._rootState.state = SystemStateEnum.lock; this._rootState.state = SystemStateEnum.lock;
// 登录回调
this._options.loginCallback = async (username: string, password: string) => { this._options.loginCallback = async (
username: string, password: string, loginCode?: {
github_code?: string,
gitee_code?: string
}
) => {
const serverUrl = config.userInfo.url + '/member/login' const serverUrl = config.userInfo.url + '/member/login'
const res: any = await fetch(serverUrl, { const res: any = await fetch(serverUrl, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
username: username, username: username,
password: password, password: password,
github_code: loginCode?.github_code,
gitee_code: loginCode?.gitee_code,
clientId: getClientId(), clientId: getClientId(),
}), }),
}); });
if (!res.ok) { if (!res.ok) {
return false return false
} }
const jsondata = await res.json(); const jsondata = await res.json();
if (jsondata.success) { if (jsondata.success) {
jsondata.data.url = config.userInfo.url jsondata.data.url = config.userInfo.url
@ -198,12 +210,16 @@ export class System {
setSystemConfig(config); setSystemConfig(config);
this._rootState.state = SystemStateEnum.open; this._rootState.state = SystemStateEnum.open;
this.refershAppList() this.refershAppList()
window.location.href = "/";
return true return true
} else { } else {
notifyError(jsondata.message) notifyError(jsondata.message)
return false return false
} }
}; };
// //const tmpCallBack = this._options.authCallback; // //const tmpCallBack = this._options.authCallback;
// this._options.authCallback = async (username: string, password: string, captcha :string) => { // this._options.authCallback = async (username: string, password: string, captcha :string) => {
// //this._rootState.state = SystemStateEnum.open; // //this._rootState.state = SystemStateEnum.open;

9
frontend/src/system/router.ts

@ -1,4 +1,4 @@
import { createWebHistory, createRouter } from "vue-router"; import { createRouter, createWebHistory } from "vue-router";
const routes = [ const routes = [
// { // {
@ -10,8 +10,13 @@ const routes = [
// name: "install", // name: "install",
// component: () => import('../views/install/install.vue') // 关于我们组件 // component: () => import('../views/install/install.vue') // 关于我们组件
// }, // },
// {
// path: "/login/github/next",
// name: "login-github",
// component: () => import("../components/desktop/GithubNext.vue"),
// },
{ {
path: '/:pathMatch(.*)*', path: "/:pathMatch(.*)*",
name: "not-found", name: "not-found",
component: () => import('../components/window/NotFound.vue') component: () => import('../components/window/NotFound.vue')
} }

11
frontend/src/system/type/type.ts

@ -1,5 +1,5 @@
import { BrowserWindowOption } from '../window/BrowserWindow';
import { MenuItem, MenuItemConstructorOptions } from '../menu/MenuItem'; import { MenuItem, MenuItemConstructorOptions } from '../menu/MenuItem';
import { BrowserWindowOption } from '../window/BrowserWindow';
export type BuiltinFeature = export type BuiltinFeature =
| 'MyComputer' | 'MyComputer'
| 'AppStore' | 'AppStore'
@ -56,7 +56,14 @@ export interface SystemOptionsCertainly {
}; };
contextMenus?: Array<MenuItemConstructorOptions | MenuItem>; contextMenus?: Array<MenuItemConstructorOptions | MenuItem>;
noPassword?: boolean; noPassword?: boolean;
loginCallback?: (username: string, password: string) => Promise<boolean>; loginCallback?: (
username: string,
password: string,
loginCode?: {
github_code?: string,
gitee_code?: string
}
) => Promise<boolean>;
} }
export type SystemOptions = SystemOptionsCertainly & { export type SystemOptions = SystemOptionsCertainly & {
[key: string]: SafeAny; [key: string]: SafeAny;

Loading…
Cancel
Save