Browse Source

add:锁屏

master
秋田弘 5 months ago
parent
commit
c54d517f6e
  1. 66
      frontend/src/components/desktop/LockDesktop.vue
  2. 244
      frontend/src/components/setting/SetAccount.vue
  3. 29
      frontend/src/system/index.ts
  4. 8
      godo/cmd/main.go
  5. 2
      godo/cmd/serve.go
  6. 27
      godo/user/user.go

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

@ -1,9 +1,11 @@
<template> <template>
<div <div
v-if="!sys._options.noPassword"
class="lockscreen" class="lockscreen"
:class="lockClassName" :class="lockClassName"
> >
<el-card <el-card
v-if="sys._rootState.state !== SystemStateEnum.lock"
class="login-box" class="login-box"
shadow="never" shadow="never"
> >
@ -322,6 +324,34 @@
</el-row> </el-row>
</div> </div>
</el-card> </el-card>
<el-card
v-else
class="lock-card"
>
<div class="avatar-container">
<el-avatar size="large">
<img
src="/logo.png"
alt="Logo"
/>
</el-avatar>
</div>
<el-form v-model="unlockForm">
<el-form-item>
<el-input
v-model="unlockForm.username"
placeholder="请输入锁屏用户名"
/>
</el-form-item>
<el-form-item>
<el-input
v-model="unlockForm.password"
placeholder="请输入锁屏密码"
/>
</el-form-item>
</el-form>
<el-button @click="lockScreen">锁屏</el-button>
</el-card>
</div> </div>
</template> </template>
@ -330,8 +360,9 @@
import { useSystem } from "@/system"; import { useSystem } from "@/system";
import { getSystemConfig, setSystemConfig } from "@/system/config"; import { getSystemConfig, setSystemConfig } from "@/system/config";
import router from "@/system/router"; import router from "@/system/router";
import { SystemStateEnum } from "@/system/type/enum";
import { RestartApp } from "@/util/goutil"; import { RestartApp } from "@/util/goutil";
import { notifyError } from "@/util/msg"; import { notifyError, notifySuccess } from "@/util/msg";
import { computed, onMounted, ref, watchEffect } from "vue"; import { computed, onMounted, ref, watchEffect } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
const route = useRoute(); const route = useRoute();
@ -346,6 +377,12 @@
localStorage.removeItem("ThirdPartyPlatform"); localStorage.removeItem("ThirdPartyPlatform");
}; };
// /screen/unlock
const unlockForm = ref({
username: "123",
password: "123",
});
const phoneForm = ref({ const phoneForm = ref({
phone: "", phone: "",
code: "", code: "",
@ -356,6 +393,20 @@
code: "", code: "",
}); });
const lockScreen = async () => {
const res = await fetch(config.apiUrl + "/user/screen/unlock", {
method: "POST",
body: JSON.stringify(unlockForm.value),
});
const data = await res.json();
if (data.code == 0) {
sys._rootState.state = SystemStateEnum.open;
notifySuccess("解锁成功");
} else {
notifyError("解锁失败");
}
};
const thirdpartyCode = ref(""); const thirdpartyCode = ref("");
const validateAndLoginByThirdparty = () => { const validateAndLoginByThirdparty = () => {
@ -1210,4 +1261,17 @@
} }
} }
} }
.lock-card {
border-radius: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 300px;
height: 300px;
background-color: #ffffff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
padding: 20px;
}
</style> </style>

244
frontend/src/components/setting/SetAccount.vue

@ -2,8 +2,12 @@
<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"
:key="index"
@click="selectItem(index)"
:class="{ active: index === activeIndex }"
>
{{ item }} {{ item }}
</li> </li>
</ul> </ul>
@ -15,20 +19,37 @@
</div> </div>
<div class="setting-item"> <div class="setting-item">
<el-select v-model="config.background.type"> <el-select v-model="config.background.type">
<el-option v-for="(item, key) in desktopOptions" :key="key" :label="item.label" :value="item.value" /> <el-option
v-for="(item, key) in desktopOptions"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> </div>
<template v-if="config.background.type === 'color'"> <template v-if="config.background.type === 'color'">
<div class="setting-item"> <div class="setting-item">
<label> </label> <label> </label>
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker> <ColorPicker
v-model:modelValue="config.background.color"
@update:modelValue="onColorChange"
></ColorPicker>
</div> </div>
</template> </template>
<template v-if="config.background.type === 'image'"> <template v-if="config.background.type === 'image'">
<div class="setting-item"> <div class="setting-item">
<ul class="image-gallery"> <ul class="image-gallery">
<li v-for="(item, index) in config.background.imageList" :key="index" <li
:class="config.background.url === item ? 'selected' : ''" @click="setBg(item)"> v-for="(item, index) in config.background
.imageList"
:key="index"
:class="
config.background.url === item
? 'selected'
: ''
"
@click="setBg(item)"
>
<img :src="item" /> <img :src="item" />
</li> </li>
</ul> </ul>
@ -43,29 +64,71 @@
<h1 class="setting-title">锁屏</h1> <h1 class="setting-title">锁屏</h1>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label> {{ t('account') }} </label> <label> {{ t("account") }} </label>
<el-input v-model="account.username" :placeholder="t('account')" clearable /> <el-input
v-model="account.username"
:placeholder="t('account')"
clearable
/>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label> {{ t('password') }} </label> <label> {{ t("password") }} </label>
<el-input v-model="account.password" type="password" :placeholder="t('password')" clearable /> <el-input
v-model="account.password"
type="password"
:placeholder="t('password')"
clearable
/>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-button @click="submit" type="primary"> <el-button
{{ t('confirm') }} @click="submit"
type="primary"
>
<span v-if="loginOrRegister">锁屏</span>
<span v-else>立即注册</span>
</el-button> </el-button>
</div> </div>
<div class="setting-item">
<label></label>
<small
v-if="loginOrRegister"
style="font-size: 12px"
>还没有账号<a
href="#"
@click.prevent="loginOrRegister = false"
><span>立即注册</span></a
></small
>
<span
v-else
style="font-size: 12px"
>
<span>注册完成</span>
<a
href="#"
@click.prevent="loginOrRegister = true"
><span>点我返回</span></a
>
</span>
</div>
</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-switch v-model="ad" active-text="开启" inactive-text="关闭" size="large" :before-change="setAd"></el-switch> <el-switch
v-model="ad"
active-text="开启"
inactive-text="关闭"
size="large"
:before-change="setAd"
></el-switch>
</div> </div>
</div> </div>
<div v-if="3 === activeIndex"> <div v-if="3 === activeIndex">
@ -75,13 +138,21 @@
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-select v-model="modelvalue"> <el-select v-model="modelvalue">
<el-option v-for="(item, key) in langList" :key="key" :label="item.label" :value="item.value" /> <el-option
v-for="(item, key) in langList"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</div> </div>
<div class="setting-item"> <div class="setting-item">
<label></label> <label></label>
<el-button @click="submitLang" type="primary"> <el-button
@click="submitLang"
type="primary"
>
{{ t("confirm") }} {{ t("confirm") }}
</el-button> </el-button>
</div> </div>
@ -91,20 +162,27 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue';
import { Dialog, useSystem } from '@/system/index.ts';
import { getSystemKey, setSystemKey, getSystemConfig, setSystemConfig } from '@/system/config'
import { ElMessageBox } from 'element-plus'
import { getLang, setLang, t } from "@/i18n"; import { getLang, setLang, t } from "@/i18n";
import {
getSystemConfig,
getSystemKey,
setSystemConfig,
setSystemKey,
} from "@/system/config";
import { useSystem } from "@/system/index.ts";
import { SystemStateEnum } from "@/system/type/enum";
import { notifyError, notifySuccess } from "@/util/msg";
import { ElMessageBox } from "element-plus";
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { locale } = useI18n(); const { locale } = useI18n();
const sys = useSystem(); const sys = useSystem();
const items = [t("background"), '锁屏设置', '广告设置', '语言']; const items = [t("background"), "锁屏设置", "广告设置", "语言"];
const activeIndex = ref(0); const activeIndex = ref(0);
const account = ref(getSystemKey('account')); const account = ref(getSystemKey("account"));
const ad = ref(account.value.ad) const ad = ref(account.value.ad);
const config: any = ref(getSystemConfig()); const config: any = ref(getSystemConfig());
const loginOrRegister = ref(true);
const langList = [ const langList = [
{ {
label: "中文", label: "中文",
@ -131,53 +209,112 @@ const desktopOptions = [
}, },
]; ];
async function submit() { async function submit() {
setSystemKey('account', account.value); // setSystemKey("account", account.value);
Dialog.showMessageBox({ // Dialog.showMessageBox({
message: t('save.success'), // message: t("save.success"),
title: t('account'), // title: t("account"),
type: 'info', // type: "info",
}).then(() => { // }).then(() => {
location.reload(); // location.reload();
// });
const data = {
username: account.value.username,
password: account.value.password,
};
const aiUrl = config.value.aiUrl;
if (!loginOrRegister.value) {
console.log(data);
try {
const response = await fetch(aiUrl + "/user/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
}); });
if (response.ok) {
const result = await response.json();
if (result.code == 0) {
notifySuccess("注册成功");
loginOrRegister.value = true;
} else {
notifyError("注册失败");
}
} else {
notifyError("注册失败");
}
} catch (error) {
notifyError("注册失败");
console.error("请求错误:", error);
}
} else {
const response = await fetch(aiUrl + "/user/screen/lock", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
if (result.code == 0) {
notifySuccess("锁屏成功");
loginOrRegister.value = true;
// sysuser
const data = {
sysuser: result.data,
lock: true,
};
localStorage.setItem("syslock", JSON.stringify(data));
sys._rootState.state = SystemStateEnum.lock;
} else {
notifyError("锁屏失败");
}
} else {
notifyError("锁屏失败");
}
}
setSystemKey("account", account.value);
} }
function setAd() { function setAd() {
const data = toRaw(account.value) const data = toRaw(account.value);
if (ad.value) { if (ad.value) {
return new Promise((resolve) => { return new Promise((resolve) => {
setTimeout(() => { setTimeout(() => {
ElMessageBox.confirm( ElMessageBox.confirm(
'广告关闭后您将收不到任何系统通知和更新提示!', "广告关闭后您将收不到任何系统通知和更新提示!",
'Warning', "Warning",
{ {
confirmButtonText: '确定关闭', confirmButtonText: "确定关闭",
cancelButtonText: '取消', cancelButtonText: "取消",
type: 'warning', type: "warning",
} }
) )
.then(() => { .then(() => {
data.ad = false data.ad = false;
setSystemKey('account', data); setSystemKey("account", data);
return resolve(true) return resolve(true);
}) })
.catch(() => { .catch(() => {
return resolve(false) return resolve(false);
}) });
}, 1000);
}, 1000) });
})
} else { } else {
data.ad = true data.ad = true;
setSystemKey('account', data); setSystemKey("account", data);
return Promise.resolve(true) return Promise.resolve(true);
} }
} }
function setBg(item: any) { function setBg(item: any) {
config.value.background.url = item config.value.background.url = item;
config.value.background.type = "image"; config.value.background.type = "image";
setSystemConfig(config.value); setSystemConfig(config.value);
sys.initBackground(); sys.initBackground();
} }
function onColorChange(color: string) { function onColorChange(color: string) {
config.value.background.color = color; config.value.background.color = color;
@ -192,6 +329,11 @@ async function submitLang() {
confirmButtonText: "OK", confirmButtonText: "OK",
}); });
} }
watch(loginOrRegister, () => {
account.value.username = "";
account.value.password = "";
});
</script> </script>
<style scoped> <style scoped>
@import "./setStyle.css"; @import "./setStyle.css";

29
frontend/src/system/index.ts

@ -103,6 +103,28 @@ export class System {
initBuiltinApp(this); initBuiltinApp(this);
initBuiltinFileOpener(this); // 注册内建文件打开器 initBuiltinFileOpener(this); // 注册内建文件打开器
await this.initSavedConfig(); // 初始化保存的配置 await this.initSavedConfig(); // 初始化保存的配置
const config = getSystemConfig();
const syslock = localStorage.getItem("syslock");
const lockData = JSON.parse(syslock || "{}");
// 判断是解锁
if (lockData.lock) {
const sysuser = lockData.sysuser;
const res = await fetch(config.apiUrl + "/user/screen/status", {
headers: {
"sysuser": sysuser,
},
});
const data = await res.json();
// data.data 为 true 表示锁屏
// 如果没锁屏,则删除 sysuser
if (!data.data) {
lockData.lock = false;
lockData.sysuser = "";
localStorage.setItem("syslock", JSON.stringify(lockData));
}
}
// 判断是否为钉钉工作台登录 // 判断是否为钉钉工作台登录
const login_type = new URLSearchParams(window.location.search).get("login_type"); const login_type = new URLSearchParams(window.location.search).get("login_type");
@ -177,6 +199,13 @@ export class System {
} }
this._rootState.state = SystemStateEnum.lock; this._rootState.state = SystemStateEnum.lock;
const syslock = localStorage.getItem("syslock");
const lockData = JSON.parse(syslock || "{}");
if (!lockData.lock) {
this._rootState.state = SystemStateEnum.open;
}
const tempCallBack = this._options.loginCallback; const tempCallBack = this._options.loginCallback;
if (!tempCallBack) { if (!tempCallBack) {
throw new Error('没有设置登录回调函数'); throw new Error('没有设置登录回调函数');

8
godo/cmd/main.go

@ -186,10 +186,10 @@ func OsStart() {
proxyRouter.HandleFunc("/frpc/status", proxy.StatusFrpcHandler).Methods(http.MethodGet) proxyRouter.HandleFunc("/frpc/status", proxy.StatusFrpcHandler).Methods(http.MethodGet)
userRouter := router.PathPrefix("/user").Subrouter() userRouter := router.PathPrefix("/user").Subrouter()
userRouter.HandleFunc("/register", user.RegisterSysUserHandler).Methods(http.MethodPost) userRouter.HandleFunc("/register", user.RegisterSysUserHandler).Methods(http.MethodPost) // 注册系统用户
userRouter.HandleFunc("/screen/lock", user.LockedScreenHandler).Methods(http.MethodPost) userRouter.HandleFunc("/screen/lock", user.LockedScreenHandler).Methods(http.MethodPost) // 锁屏
userRouter.HandleFunc("/screen/unlock", user.UnLockScreenHandler).Methods(http.MethodPost) userRouter.HandleFunc("/screen/unlock", user.UnLockScreenHandler).Methods(http.MethodPost) // 解锁
userRouter.HandleFunc("/screen/status", user.CheckLockedScreenHandler).Methods(http.MethodGet) userRouter.HandleFunc("/screen/status", user.CheckLockedScreenHandler).Methods(http.MethodGet) // 检查锁屏状态
// 注册根路径的处理函数 // 注册根路径的处理函数
distFS, _ := fs.Sub(deps.Frontendassets, "dist") distFS, _ := fs.Sub(deps.Frontendassets, "dist")

2
godo/cmd/serve.go

@ -115,7 +115,7 @@ func recoverMiddleware(next http.Handler) http.Handler {
// CORS 中间件 // CORS 中间件
func corsMiddleware() mux.MiddlewareFunc { func corsMiddleware() mux.MiddlewareFunc {
allowHeaders := "Content-Type, Accept, Authorization, Origin, Pwd" allowHeaders := "Content-Type, Accept, Authorization, Origin, Pwd, sysuser"
allowMethods := "GET, POST, PUT, DELETE, OPTIONS" allowMethods := "GET, POST, PUT, DELETE, OPTIONS"
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {

27
godo/user/user.go

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"godo/libs" "godo/libs"
"godo/model" "godo/model"
"io"
"log"
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
@ -21,20 +23,35 @@ func RegisterSysUserHandler(w http.ResponseWriter, r *http.Request) {
} }
var user model.SysUser var user model.SysUser
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
// 获取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
log.Println("读取请求体错误:", err)
libs.ErrorMsg(w, "invalid input")
return
}
// 解析请求体
if err := json.Unmarshal(body, &user); err != nil {
log.Println("解析请求体错误:", err)
libs.ErrorMsg(w, "invalid input") libs.ErrorMsg(w, "invalid input")
return return
} }
// 检查用户名和密码是否为空
if user.Username == "" || user.Password == "" { if user.Username == "" || user.Password == "" {
libs.ErrorMsg(w, "username or password is empty") libs.ErrorMsg(w, "username or password is empty")
return return
} }
// 创建用户
if err := model.Db.Create(&user).Error; err != nil { if err := model.Db.Create(&user).Error; err != nil {
libs.ErrorMsg(w, "failed to create user") libs.ErrorMsg(w, "failed to create user")
return return
} }
// 返回成功消息
libs.SuccessMsg(w, user.ID, "") libs.SuccessMsg(w, user.ID, "")
} }
@ -63,7 +80,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Header().Set("sysuser", userEncodeStr) // w.Header().Set("sysuser", userEncodeStr)
// 登录成功,锁屏状态设置为 true // 登录成功,锁屏状态设置为 true
mu.Lock() mu.Lock()
@ -73,7 +90,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
userLockedScreen[user.ID] = true userLockedScreen[user.ID] = true
mu.Unlock() mu.Unlock()
libs.SuccessMsg(w, nil, "system locked") libs.SuccessMsg(w, userEncodeStr, "system locked")
} }
// 系统用户登录(解锁) // 系统用户登录(解锁)
@ -115,9 +132,11 @@ func CheckLockedScreenHandler(w http.ResponseWriter, r *http.Request) {
decoderUid := r.Header.Get("sysuser") decoderUid := r.Header.Get("sysuser")
if decoderUid == "" { if decoderUid == "" {
libs.ErrorMsg(w, "invalid uid") //获取锁屏状态
libs.SuccessMsg(w, false, "")
return return
} }
uidStr, err := libs.DecodeFile("godoos", decoderUid) uidStr, err := libs.DecodeFile("godoos", decoderUid)
if err != nil { if err != nil {
libs.ErrorMsg(w, "invalid uid") libs.ErrorMsg(w, "invalid uid")

Loading…
Cancel
Save