Browse Source

add:锁屏

master
秋田弘 5 months ago
parent
commit
c54d517f6e
  1. 66
      frontend/src/components/desktop/LockDesktop.vue
  2. 500
      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>
<div
v-if="!sys._options.noPassword"
class="lockscreen"
:class="lockClassName"
>
<el-card
v-if="sys._rootState.state !== SystemStateEnum.lock"
class="login-box"
shadow="never"
>
@ -322,6 +324,34 @@
</el-row>
</div>
</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>
</template>
@ -330,8 +360,9 @@
import { useSystem } from "@/system";
import { getSystemConfig, setSystemConfig } from "@/system/config";
import router from "@/system/router";
import { SystemStateEnum } from "@/system/type/enum";
import { RestartApp } from "@/util/goutil";
import { notifyError } from "@/util/msg";
import { notifyError, notifySuccess } from "@/util/msg";
import { computed, onMounted, ref, watchEffect } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
@ -346,6 +377,12 @@
localStorage.removeItem("ThirdPartyPlatform");
};
// /screen/unlock
const unlockForm = ref({
username: "123",
password: "123",
});
const phoneForm = ref({
phone: "",
code: "",
@ -356,6 +393,20 @@
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 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>

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

@ -1,199 +1,341 @@
<template>
<div class="container">
<div class="nav">
<ul>
<li v-for="(item, index) in items" :key="index" @click="selectItem(index)"
:class="{ active: index === activeIndex }">
{{ item }}
</li>
</ul>
</div>
<div class="setting">
<div v-if="0 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("background") }}</h1>
</div>
<div class="setting-item">
<el-select v-model="config.background.type">
<el-option v-for="(item, key) in desktopOptions" :key="key" :label="item.label" :value="item.value" />
</el-select>
</div>
<template v-if="config.background.type === 'color'">
<div class="setting-item">
<label> </label>
<ColorPicker v-model:modelValue="config.background.color" @update:modelValue="onColorChange"></ColorPicker>
</div>
</template>
<template v-if="config.background.type === 'image'">
<div class="setting-item">
<ul class="image-gallery">
<li v-for="(item, index) in config.background.imageList" :key="index"
:class="config.background.url === item ? 'selected' : ''" @click="setBg(item)">
<img :src="item" />
</li>
</ul>
</div>
<div class="setting-item">
<label> </label>
</div>
</template>
</div>
<div v-if="1 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">锁屏</h1>
</div>
<div class="setting-item">
<label> {{ t('account') }} </label>
<el-input v-model="account.username" :placeholder="t('account')" clearable />
</div>
<div class="setting-item">
<label> {{ t('password') }} </label>
<el-input v-model="account.password" type="password" :placeholder="t('password')" clearable />
</div>
<div class="container">
<div class="nav">
<ul>
<li
v-for="(item, index) in items"
:key="index"
@click="selectItem(index)"
:class="{ active: index === activeIndex }"
>
{{ item }}
</li>
</ul>
</div>
<div class="setting">
<div v-if="0 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("background") }}</h1>
</div>
<div class="setting-item">
<el-select v-model="config.background.type">
<el-option
v-for="(item, key) in desktopOptions"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<template v-if="config.background.type === 'color'">
<div class="setting-item">
<label> </label>
<ColorPicker
v-model:modelValue="config.background.color"
@update:modelValue="onColorChange"
></ColorPicker>
</div>
</template>
<template v-if="config.background.type === 'image'">
<div class="setting-item">
<ul class="image-gallery">
<li
v-for="(item, index) in config.background
.imageList"
:key="index"
:class="
config.background.url === item
? 'selected'
: ''
"
@click="setBg(item)"
>
<img :src="item" />
</li>
</ul>
</div>
<div class="setting-item">
<label> </label>
</div>
</template>
</div>
<div v-if="1 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">锁屏</h1>
</div>
<div class="setting-item">
<label> {{ t("account") }} </label>
<el-input
v-model="account.username"
:placeholder="t('account')"
clearable
/>
</div>
<div class="setting-item">
<label> {{ t("password") }} </label>
<el-input
v-model="account.password"
type="password"
:placeholder="t('password')"
clearable
/>
</div>
<div class="setting-item">
<label></label>
<el-button @click="submit" type="primary">
{{ t('confirm') }}
</el-button>
</div>
</div>
<div v-if="2 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">广告与更新提示</h1>
<div class="setting-item">
<label></label>
<el-button
@click="submit"
type="primary"
>
<span v-if="loginOrRegister">锁屏</span>
<span v-else>立即注册</span>
</el-button>
</div>
</div>
<div class="setting-item">
<label></label>
<el-switch v-model="ad" active-text="开启" inactive-text="关闭" size="large" :before-change="setAd"></el-switch>
</div>
</div>
<div v-if="3 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("language") }}</h1>
</div>
<div class="setting-item">
<label></label>
<el-select v-model="modelvalue">
<el-option v-for="(item, key) in langList" :key="key" :label="item.label" :value="item.value" />
</el-select>
</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 v-if="2 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">广告与更新提示</h1>
</div>
<div class="setting-item">
<label></label>
<el-switch
v-model="ad"
active-text="开启"
inactive-text="关闭"
size="large"
:before-change="setAd"
></el-switch>
</div>
</div>
<div v-if="3 === activeIndex">
<div class="setting-item">
<h1 class="setting-title">{{ t("language") }}</h1>
</div>
<div class="setting-item">
<label></label>
<el-select v-model="modelvalue">
<el-option
v-for="(item, key) in langList"
:key="key"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="setting-item">
<label></label>
<el-button @click="submitLang" type="primary">
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</div>
<div class="setting-item">
<label></label>
<el-button
@click="submitLang"
type="primary"
>
{{ t("confirm") }}
</el-button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
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";
const { locale } = useI18n();
const sys = useSystem();
const items = [t("background"), "锁屏设置", "广告设置", "语言"];
const activeIndex = ref(0);
const account = ref(getSystemKey("account"));
const ad = ref(account.value.ad);
const config: any = ref(getSystemConfig());
const loginOrRegister = ref(true);
const langList = [
{
label: "中文",
value: "zh-cn",
},
{
label: "English",
value: "en",
},
];
const currentLang = getLang();
const modelvalue = ref(currentLang);
const selectItem = (index: number) => {
activeIndex.value = index;
};
const desktopOptions = [
{
label: t("image"),
value: "image",
},
{
label: t("color"),
value: "color",
},
];
async function submit() {
// setSystemKey("account", account.value);
// Dialog.showMessageBox({
// message: t("save.success"),
// title: t("account"),
// type: "info",
// }).then(() => {
// location.reload();
// });
const data = {
username: account.value.username,
password: account.value.password,
};
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 { useI18n } from "vue-i18n";
const { locale } = useI18n();
const sys = useSystem();
const items = [t("background"), '锁屏设置', '广告设置', '语言'];
const activeIndex = ref(0);
const account = ref(getSystemKey('account'));
const ad = ref(account.value.ad)
const config: any = ref(getSystemConfig());
const langList = [
{
label: "中文",
value: "zh-cn",
},
{
label: "English",
value: "en",
},
];
const currentLang = getLang();
const modelvalue = ref(currentLang);
const selectItem = (index: number) => {
activeIndex.value = index;
};
const desktopOptions = [
{
label: t("image"),
value: "image",
},
{
label: t("color"),
value: "color",
},
];
async function submit() {
setSystemKey('account', account.value);
Dialog.showMessageBox({
message: t('save.success'),
title: t('account'),
type: 'info',
}).then(() => {
location.reload();
});
}
function setAd() {
const data = toRaw(account.value)
if (ad.value) {
return new Promise((resolve) => {
setTimeout(() => {
ElMessageBox.confirm(
'广告关闭后您将收不到任何系统通知和更新提示!',
'Warning',
{
confirmButtonText: '确定关闭',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
data.ad = false
setSystemKey('account', data);
return resolve(true)
})
.catch(() => {
return resolve(false)
})
const aiUrl = config.value.aiUrl;
if (!loginOrRegister.value) {
console.log(data);
}, 1000)
})
} else {
data.ad = true
setSystemKey('account', data);
return Promise.resolve(true)
}
try {
const response = await fetch(aiUrl + "/user/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
}
function setBg(item: any) {
config.value.background.url = item
config.value.background.type = "image";
setSystemConfig(config.value);
sys.initBackground();
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),
});
}
function onColorChange(color: string) {
config.value.background.color = color;
config.value.background.type = "color";
setSystemConfig(config.value);
sys.initBackground();
}
async function submitLang() {
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() {
const data = toRaw(account.value);
if (ad.value) {
return new Promise((resolve) => {
setTimeout(() => {
ElMessageBox.confirm(
"广告关闭后您将收不到任何系统通知和更新提示!",
"Warning",
{
confirmButtonText: "确定关闭",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
data.ad = false;
setSystemKey("account", data);
return resolve(true);
})
.catch(() => {
return resolve(false);
});
}, 1000);
});
} else {
data.ad = true;
setSystemKey("account", data);
return Promise.resolve(true);
}
}
function setBg(item: any) {
config.value.background.url = item;
config.value.background.type = "image";
setSystemConfig(config.value);
sys.initBackground();
}
function onColorChange(color: string) {
config.value.background.color = color;
config.value.background.type = "color";
setSystemConfig(config.value);
sys.initBackground();
}
async function submitLang() {
setLang(modelvalue.value);
locale.value = modelvalue.value;
ElMessageBox.alert(t("save.success"), t("language"), {
confirmButtonText: "OK",
});
}
watch(loginOrRegister, () => {
account.value.username = "";
account.value.password = "";
});
</script>
<style scoped>
@import "./setStyle.css";
@import "@/assets/imglist.scss";
@import "./setStyle.css";
@import "@/assets/imglist.scss";
</style>

29
frontend/src/system/index.ts

@ -103,6 +103,28 @@ export class System {
initBuiltinApp(this);
initBuiltinFileOpener(this); // 注册内建文件打开器
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");
@ -177,6 +199,13 @@ export class System {
}
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;
if (!tempCallBack) {
throw new Error('没有设置登录回调函数');

8
godo/cmd/main.go

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

2
godo/cmd/serve.go

@ -115,7 +115,7 @@ func recoverMiddleware(next http.Handler) http.Handler {
// CORS 中间件
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"
return func(next http.Handler) http.Handler {

27
godo/user/user.go

@ -4,6 +4,8 @@ import (
"encoding/json"
"godo/libs"
"godo/model"
"io"
"log"
"net/http"
"strconv"
"sync"
@ -21,20 +23,35 @@ func RegisterSysUserHandler(w http.ResponseWriter, r *http.Request) {
}
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")
return
}
// 检查用户名和密码是否为空
if user.Username == "" || user.Password == "" {
libs.ErrorMsg(w, "username or password is empty")
return
}
// 创建用户
if err := model.Db.Create(&user).Error; err != nil {
libs.ErrorMsg(w, "failed to create user")
return
}
// 返回成功消息
libs.SuccessMsg(w, user.ID, "")
}
@ -63,7 +80,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("sysuser", userEncodeStr)
// w.Header().Set("sysuser", userEncodeStr)
// 登录成功,锁屏状态设置为 true
mu.Lock()
@ -73,7 +90,7 @@ func LockedScreenHandler(w http.ResponseWriter, r *http.Request) {
userLockedScreen[user.ID] = true
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")
if decoderUid == "" {
libs.ErrorMsg(w, "invalid uid")
//获取锁屏状态
libs.SuccessMsg(w, false, "")
return
}
uidStr, err := libs.DecodeFile("godoos", decoderUid)
if err != nil {
libs.ErrorMsg(w, "invalid uid")

Loading…
Cancel
Save