prr 7 months ago
parent
commit
8d2422172d
  1. 2
      frontend/components.d.ts
  2. 89
      frontend/src/components/chat/Chat.vue
  3. 126
      frontend/src/components/chat/ChatMenu.vue
  4. 354
      frontend/src/components/chat/ChatMessage.vue
  5. 10
      frontend/src/components/chat/ChatMsgList.vue
  6. 47
      frontend/src/components/chat/ChatUserList.vue
  7. 27
      frontend/src/components/window/WindowTemplate.vue
  8. 313
      frontend/src/stores/chat.ts
  9. 1
      frontend/src/stores/localchat.ts
  10. 317
      frontend/src/stores/upgrade.ts
  11. 66
      frontend/src/system/index.ts
  12. 1
      frontend/tsconfig.json
  13. 2
      godo/cmd/main.go
  14. 23
      godo/files/fs.go
  15. 16
      godo/files/os.go
  16. 56
      godo/files/pwdfile.go

2
frontend/components.d.ts

@ -51,6 +51,7 @@ declare module 'vue' {
ElAside: typeof import('element-plus/es')['ElAside'] ElAside: typeof import('element-plus/es')['ElAside']
ElAvatar: typeof import('element-plus/es')['ElAvatar'] ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElBadge: typeof import('element-plus/es')['ElBadge'] ElBadge: typeof import('element-plus/es')['ElBadge']
ElBu: typeof import('element-plus/es')['ElBu']
ElButton: typeof import('element-plus/es')['ElButton'] ElButton: typeof import('element-plus/es')['ElButton']
ElCard: typeof import('element-plus/es')['ElCard'] ElCard: typeof import('element-plus/es')['ElCard']
ElCarousel: typeof import('element-plus/es')['ElCarousel'] ElCarousel: typeof import('element-plus/es')['ElCarousel']
@ -84,6 +85,7 @@ declare module 'vue' {
ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn']
ElText: typeof import('element-plus/es')['ElText'] ElText: typeof import('element-plus/es')['ElText']
ElTooltip: typeof import('element-plus/es')['ElTooltip'] ElTooltip: typeof import('element-plus/es')['ElTooltip']
ElTransfer: typeof import('element-plus/es')['ElTransfer']
Error: typeof import('./src/components/taskbar/Error.vue')['default'] Error: typeof import('./src/components/taskbar/Error.vue')['default']
FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default'] FileIcon: typeof import('./src/components/builtin/FileIcon.vue')['default']
FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default'] FileIconImg: typeof import('./src/components/builtin/FileIconImg.vue')['default']

89
frontend/src/components/chat/Chat.vue

@ -6,8 +6,22 @@
const workUrl = getWorkflowUrl(); const workUrl = getWorkflowUrl();
onMounted(() => { onMounted(() => {
store.initChat(); store.initChat();
// store.initSSE();
}); });
const generateData = () => {
const data = [];
for (let i = 1; i <= 15; i++) {
data.push({
key: i,
label: ` ${i}`,
disabled: i % 4 === 0,
});
}
return data;
};
const data = generateData();
const value = ref([]);
</script> </script>
<template> <template>
<el-container class="container"> <el-container class="container">
@ -30,6 +44,13 @@
class="search-input" class="search-input"
v-model="store.search" v-model="store.search"
/> />
<!-- 邀请群聊 -->
<button
class="inviteGroupChats"
@click="store.setGroupChatDialogVisible(true)"
>
<el-icon><Plus /></el-icon>
</button>
</el-header> </el-header>
<!--好友列表--> <!--好友列表-->
<el-main class="list"> <el-main class="list">
@ -42,7 +63,7 @@
</el-container> </el-container>
<el-container class="chat-box"> <el-container class="chat-box">
<chat-box v-if="store.currentNavId < 1" /> <chat-box v-if="store.currentNavId < 1" />
<chat-user-info v-if="store.currentNavId ==1"></chat-user-info> <chat-user-info v-if="store.currentNavId == 1"></chat-user-info>
</el-container> </el-container>
<el-container <el-container
class="chat-setting" class="chat-setting"
@ -60,6 +81,28 @@
<ChatUserSetting /> <ChatUserSetting />
</el-container> </el-container>
</el-container> </el-container>
<!-- 群聊弹窗 -->
<el-dialog
v-model="store.groupChatDialogVisible"
title="发起群聊"
width="600px"
>
<div class="transfer">
<el-transfer class="transfer-box" v-model="value" :data="data" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="store.groupChatDialogVisible = false"
>取消</el-button
>
<el-button
type="primary"
@click="createGroupChat"
>确定</el-button
>
</span>
</template>
</el-dialog>
</template> </template>
<style scoped> <style scoped>
.container { .container {
@ -73,7 +116,7 @@
.menu { .menu {
width: 55px; width: 55px;
background-color: white; background-color: #f0f0f0;
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
-webkit-app-region: drag; -webkit-app-region: drag;
@ -86,24 +129,42 @@
border-right: 1px solid #edebeb; border-right: 1px solid #edebeb;
overflow-y: hidden; overflow-y: hidden;
overflow-x: hidden; overflow-x: hidden;
background-color: #f7f7f7;
} }
.search { .search {
width: 100%; display: flex;
align-items: center;
justify-content: space-evenly;
width: 90%;
/* 占据整个宽度 */ /* 占据整个宽度 */
height: 50px; height: 50px;
padding: 0; padding: 0;
-webkit-app-region: drag; -webkit-app-region: drag;
} }
.inviteGroupChats {
width: 40px;
height: 30px;
border: none;
border-radius: 4px;
background-color: #f0f0f0;
}
.transfer-box {
height: 220px;
display: flex;
justify-content: center;
align-items: center;
}
.search-input { .search-input {
width: calc(100% - 20px); width: calc(100% - 20px);
/* 减去左右边距 */ /* 减去左右边距 */
margin: 10px; margin: 10px;
height: 32px;
-webkit-app-region: no-drag; -webkit-app-region: no-drag;
--el-input-placeholder-color: #818181 !important; --el-input-placeholder-color: #bfbfbf !important;
--el-input-icon-color: #5d5d5d !important; --el-input-icon-color: #bfbfbf !important;
} }
.list { .list {
@ -134,11 +195,11 @@
border: none; border: none;
} }
.no-message-container { .no-message-container {
height: 100%; height: 100%;
margin: 120px auto; margin: 120px auto;
text-align: center; text-align: center;
font-size:14px; font-size: 14px;
justify-content: center; justify-content: center;
} }
</style> </style>

126
frontend/src/components/chat/ChatMenu.vue

@ -1,70 +1,116 @@
<script setup> <script setup>
import {ref} from "vue";
import { useChatStore } from "@/stores/chat"; import { useChatStore } from "@/stores/chat";
import {Setting as SettingIcon} from "@element-plus/icons-vue"; import { Setting as SettingIcon } from "@element-plus/icons-vue";
const store = useChatStore()
const getOnline = () => { const store = useChatStore();
store.getOnlineUsers()
store.setCurrentNavId(1)
}
</script> </script>
<template> <template>
<el-row> <el-row>
<el-avatar shape="square" :size="40" class="userAvatar" :src="store.userInfo.avatar"/> <el-avatar
shape="square"
:size="40"
class="userAvatar"
:src="store.userInfo.avatar"
/>
</el-row> </el-row>
<el-row @click="store.setCurrentNavId(0)"> <el-row @click="store.setCurrentNavId(0)">
<el-icon v-if="store.currentNavId === 0" class="menu-icon-on"> <div class="menu-icon-box">
<ChatLineRound /> <el-icon
</el-icon> v-if="store.currentNavId === 0"
<el-icon v-else class="menu-icon"> class="menu-icon-on"
<ChatRound /> >
</el-icon> <ChatLineRound />
</el-icon>
<el-icon
v-else
class="menu-icon"
>
<ChatRound />
</el-icon>
</div>
</el-row> </el-row>
<el-row @click="getOnline"> <el-row @click="store.setCurrentNavId(1)">
<el-icon v-if="store.currentNavId === 1" class="menu-icon-on"> <div class="menu-icon-box">
<UserFilled /> <el-icon
</el-icon> v-if="store.currentNavId === 1"
<el-icon v-else class="menu-icon"> class="menu-icon-on"
<User /> >
</el-icon> <UserFilled />
</el-icon>
<el-icon
v-else
class="menu-icon"
>
<User />
</el-icon>
</div>
</el-row> </el-row>
<el-row @click="store.setCurrentNavId(2)"> <el-row @click="store.setCurrentNavId(2)">
<el-icon v-if="store.currentNavId === 2" class="menu-icon-on"> <div class="menu-icon-box">
<Platform /> <el-icon
</el-icon> v-if="store.currentNavId === 2"
<el-icon v-else class="menu-icon"> class="menu-icon-on"
<Monitor /> >
</el-icon> <Platform />
</el-icon>
<el-icon
v-else
class="menu-icon"
>
<Monitor />
</el-icon>
</div>
</el-row> </el-row>
<el-row @click="store.setCurrentNavId(5)"> <el-row @click="store.setCurrentNavId(5)">
<el-icon v-if="store.currentNavId === 5" class="menu-icon-on"> <div class="menu-icon-box">
<SettingIcon /> <el-icon
</el-icon> v-if="store.currentNavId === 5"
<el-icon v-else class="menu-icon"> class="menu-icon-on"
<SettingIcon /> >
</el-icon> <SettingIcon />
</el-icon>
<el-icon
v-else
class="menu-icon"
>
<SettingIcon />
</el-icon>
</div>
</el-row> </el-row>
</template> </template>
<style scoped> <style scoped>
.userAvatar { .userAvatar {
margin: 30px auto 10px; margin: 30px auto 10px;
-webkit-app-region: no-drag -webkit-app-region: no-drag;
}
.menu-icon-box {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin: 10px auto;
border-radius: 15%;
/* background-color: #bae7ff; */
transition: all 0.5s;
}
.menu-icon-box:hover {
background-color: #d9d9d9;
} }
.menu-icon { .menu-icon {
margin: 20px auto; margin: 0px auto;
font-size: 25px; font-size: 25px;
color: #8F8F8F;
cursor: pointer; cursor: pointer;
} }
.menu-icon-on { .menu-icon-on {
margin: 20px auto; margin: 0px auto;
font-size: 25px; font-size: 25px;
color: #0078d4; color: #1890ff;
cursor: pointer; cursor: pointer;
} }
</style>
</style>

354
frontend/src/components/chat/ChatMessage.vue

@ -1,158 +1,210 @@
<script setup lang="ts"> <script setup lang="ts">
import { useChatStore } from '@/stores/chat'; import { useChatStore } from "@/stores/chat";
const store = useChatStore();
const chatHistory = computed(() => store.chatHistory as any);
const store = useChatStore();
</script> </script>
<template> <template>
<div v-for="item in store.chatHistory" :key="item.id"> <div
<div v-if="!item.isme" class="chat-item"> v-for="item in chatHistory"
<el-row> :key="item.id"
<el-col :span="8" /> >
<el-col :span="14"> <div
<el-row> v-if="!item.isme"
<el-col :span="24"> class="chat-item"
<div class="chat-name-me">{{ item.userInfo.username }}</div> >
</el-col> <el-row>
</el-row> <el-col :span="8" />
<div class="bubble-me" @contextmenu.prevent="store.showContextMenu($event, item.id)"> <el-col :span="14">
<div class="chat-font"> <el-row>
{{ item.content }} <el-col :span="24">
</div> <div class="chat-name-me">
</div> {{ item.userInfo.username }}
</el-col> </div>
<el-col :span="2"> </el-col>
<div class="chat-avatar"> </el-row>
<el-avatar shape="square" style="margin: 0;float: left" :size="32" class="userAvatar" <div
:src="item.userInfo.avatar" /> class="bubble-me"
</div> @contextmenu.prevent="
</el-col> store.showContextMenu($event, item.id)
</el-row> "
</div> >
<div v-else class="chat-item"> <div class="chat-font">
<el-row> {{ item.content }}
<el-col :span="2"> </div>
<div class="chat-avatar"> </div>
<el-avatar shape="square" style="margin: 0;float: right" :size="32" class="userAvatar" </el-col>
:src="item.userInfo.avatar" /> <el-col :span="2">
</div> <div class="chat-avatar">
</el-col> <el-avatar
<el-col :span="14"> shape="square"
<el-row> style="margin: 0; float: left"
<el-col :span="24"> :size="32"
<div class="chat-name-other">{{ item.userInfo.username }}</div> class="userAvatar"
</el-col> :src="item.userInfo.avatar"
</el-row> />
<div class="bubble-other"> </div>
<div class="chat-font"> </el-col>
{{ item.content }} </el-row>
</div> </div>
</div> <div
</el-col> v-else
<el-col :span="8" /> class="chat-item"
</el-row> >
</div> <el-row>
<div v-if="item.type === 1" class="withdraw"> <el-col :span="2">
{{ item.userInfo.id === store.targetUserId ? "你" : item.userInfo.username }}撤回了一条消息 <div class="chat-avatar">
</div> <el-avatar
</div> shape="square"
<!--悬浮菜单--> style="margin: 0; float: right"
<div class="context-menu" v-if="store.contextMenu.visible" :size="32"
:style="{ top: `${store.contextMenu.y}px`, left: `${store.contextMenu.x}px` }"> class="userAvatar"
<div v-for="contextItem in store.contextMenu.list" :key="contextItem.id" class="context-menu-item"> :src="item.userInfo.avatar"
<div class="context-menu-item-font" @click="store.handleContextMenu(contextItem)"> />
{{ contextItem.label }} </div>
</div> </el-col>
</div> <el-col :span="14">
</div> <el-row>
<el-col :span="24">
<div class="chat-name-other">
{{ item.userInfo.username }}
</div>
</el-col>
</el-row>
<div class="bubble-other">
<div class="chat-font">
{{ item.content }}
</div>
</div>
</el-col>
<el-col :span="8" />
</el-row>
</div>
<div
v-if="item.type === 1"
class="withdraw"
>
{{
item.userInfo.id === store.targetUserId
? "你"
: item.userInfo.username
}}撤回了一条消息
</div>
</div>
<!--悬浮菜单-->
<div
class="context-menu"
v-if="store.contextMenu.visible"
:style="{
top: `${store.contextMenu.y}px`,
left: `${store.contextMenu.x}px`,
}"
>
<div
v-for="contextItem in store.contextMenu.list"
:key="contextItem.id"
class="context-menu-item"
>
<div
class="context-menu-item-font"
@click="store.handleContextMenu()"
>
{{ contextItem.label }}
</div>
</div>
</div>
</template> </template>
<style scoped> <style scoped>
.bubble-me { .bubble-me {
background-color: #95EC69; background-color: #95ec69;
float: right; float: right;
border-radius: 4px; border-radius: 4px;
margin-right: 5px; margin-right: 5px;
margin-top: 5px; margin-top: 5px;
} }
.bubble-me:hover { .bubble-me:hover {
background-color: #89D961; background-color: #89d961;
} }
.chat-name-me { .chat-name-me {
font-size: 14px; font-size: 14px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
color: #B2B2B2; color: #b2b2b2;
float: right; float: right;
margin-right: 5px; margin-right: 5px;
} }
.bubble-other { .bubble-other {
background-color: #FFFFFF; background-color: #ffffff;
float: left; float: left;
border-radius: 4px; border-radius: 4px;
margin-left: 5px; margin-left: 5px;
margin-top: 5px; margin-top: 5px;
} }
.bubble-other:hover { .bubble-other:hover {
background-color: #EBEBEB; background-color: #ebebeb;
} }
.chat-name-other { .chat-name-other {
font-size: 14px; font-size: 14px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
color: #B2B2B2; color: #b2b2b2;
float: left; float: left;
margin-left: 5px; margin-left: 5px;
} }
.chat-font { .chat-font {
margin: 8px; margin: 8px;
font-size: 15px; font-size: 15px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
} }
.chat-avatar { .chat-avatar {
margin: 5px; margin: 5px;
} }
.chat-item { .chat-item {
margin: 5px; margin: 5px;
} }
.withdraw { .withdraw {
text-align: center; text-align: center;
font-size: 13px; font-size: 13px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
color: #999999; color: #999999;
line-height: 3.2; line-height: 3.2;
} }
.context-menu { .context-menu {
position: fixed; position: fixed;
background-color: white; background-color: white;
z-index: 9999; z-index: 9999;
border: 1px solid #cccc; border: 1px solid #cccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
} }
.context-menu-item { .context-menu-item {
width: 80px; width: 80px;
height: 30px; height: 30px;
} }
.context-menu-item:hover { .context-menu-item:hover {
background-color: #E2E2E2; background-color: #e2e2e2;
} }
.context-menu-item-font { .context-menu-item-font {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
line-height: 2.2; line-height: 2.2;
} }
</style> </style>

10
frontend/src/components/chat/ChatMsgList.vue

@ -4,7 +4,7 @@
class="list-item" class="list-item"
@click="store.changeChatList(item.id)" @click="store.changeChatList(item.id)"
:style="{ :style="{
backgroundColor: item.id === store.targetUserId ? '#C4C4C4' : '', backgroundColor: item.id === store.targetUserId ? '#bae7ff' : '',
}" }"
> >
<el-row> <el-row>
@ -60,13 +60,17 @@ const id = ref("1");
<style scoped> <style scoped>
.list-item { .list-item {
width: 100%; width: 94%;
height: 60px; height: 60px;
display: flex; display: flex;
margin: 0 auto;
border-radius: 4px;
transition: all 0.5s;
margin-top: 5px;
} }
.list-item:hover { .list-item:hover {
background-color: #d0d0d0; background-color: #bae7ff;
} }
.avatar { .avatar {

47
frontend/src/components/chat/ChatUserList.vue

@ -11,14 +11,19 @@
</script> </script>
<template> <template>
<el-collapse <el-collapse v-model="store.activeNames">
v-model="store.activeNames"
@change="handleChange"
>
<el-collapse-item name="1"> <el-collapse-item name="1">
<template #title> <template #title>
<span v-if="store.userList.length > 0" class="title">同事{{store.userList.length}}</span> <span
<span v-else class="title">同事</span> v-if="store.userList.length > 0"
class="title"
>同事{{ store.userList.length }}</span
>
<span
v-else
class="title"
>同事</span
>
</template> </template>
<div v-if="store.userList.length > 0"> <div v-if="store.userList.length > 0">
<div <div
@ -30,7 +35,7 @@
@click="store.changeChatList(item.id)" @click="store.changeChatList(item.id)"
:style="{ :style="{
backgroundColor: backgroundColor:
item.id === store.targetUserId ? '#C4C4C4' : '', item.id === store.targetUserId ? '#bae7ff' : '',
}" }"
> >
<el-row> <el-row>
@ -47,7 +52,10 @@
<el-col :span="18"> <el-col :span="18">
<div class="previewName"> <div class="previewName">
{{ item.nickname }} {{ item.nickname }}
<el-icon class="online-icon" v-if="item.isOnline"> <el-icon
class="online-icon"
v-if="item.isOnline"
>
<CircleCheckFilled /> <CircleCheckFilled />
</el-icon> </el-icon>
</div> </div>
@ -72,8 +80,16 @@
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="2"> <el-collapse-item name="2">
<template #title> <template #title>
<span v-if="store.groupList.length > 0" class="title">部门{{store.groupList.length}}</span> <span
<span v-else class="title">部门</span> v-if="store.groupList.length > 0"
class="title"
>部门{{ store.groupList.length }}</span
>
<span
v-else
class="title"
>部门</span
>
</template> </template>
<div v-if="store.groupList.length > 0"> <div v-if="store.groupList.length > 0">
<div <div
@ -147,13 +163,16 @@
padding-left: 10px; padding-left: 10px;
} }
.list-item { .list-item {
width: 100%; width: 94%;
height: 60px; height: 60px;
border: 0.5px solid #d0d0d0; margin: 0 auto;
border-radius: 4px;
transition: all 0.5s;
margin-bottom: 5px;
} }
.list-item:hover { .list-item:hover {
background-color: #d0d0d0; background-color: #bae7ff;
} }
.avatar { .avatar {
@ -218,4 +237,4 @@
text-align: center; text-align: center;
color: #999999; color: #999999;
} }
</style> </style>

27
frontend/src/components/window/WindowTemplate.vue

@ -54,12 +54,12 @@
import { onUnmounted, provide, ref } from "vue"; import { onUnmounted, provide, ref } from "vue";
import { onMounted, computed, UnwrapNestedRefs } from "vue"; import { onMounted, computed, UnwrapNestedRefs } from "vue";
import { WindowStateEnum } from "@/system/window/BrowserWindow"; import { WindowStateEnum } from "@/system/window/BrowserWindow";
import { ScaleElement } from "@/system/window/dom/ScaleElement"; import { ScaleElement } from "@/system/window/dom/ScaleElement";
import { BrowserWindow } from "@/system/window/BrowserWindow"; import { BrowserWindow } from "@/system/window/BrowserWindow";
import { emitEvent } from "@/system/event"; import { emitEvent } from "@/system/event";
import { useSystem } from "@/system"; import { useSystem } from "@/system";
import { vDragable } from "@/system/window/MakeDragable"; import { vDragable } from "@/system/window/MakeDragable";
const sys = useSystem(); const sys = useSystem();
const props = defineProps<{ const props = defineProps<{
browserWindow: UnwrapNestedRefs<BrowserWindow>; browserWindow: UnwrapNestedRefs<BrowserWindow>;
@ -67,7 +67,6 @@ const props = defineProps<{
const browserWindow = props.browserWindow; const browserWindow = props.browserWindow;
const windowInfo = browserWindow.windowInfo; const windowInfo = browserWindow.windowInfo;
// windowid
provide("browserWindow", browserWindow); provide("browserWindow", browserWindow);
provide("system", sys); provide("system", sys);
@ -96,7 +95,6 @@ onMounted(() => {
height: computed(() => windowInfo.height + "px"), height: computed(() => windowInfo.height + "px"),
left: computed(() => windowInfo.x + "px"), left: computed(() => windowInfo.x + "px"),
top: computed(() => windowInfo.y + "px"), top: computed(() => windowInfo.y + "px"),
zIndex: computed(() => { zIndex: computed(() => {
if (windowInfo.alwaysOnTop) { if (windowInfo.alwaysOnTop) {
return 9999; return 9999;
@ -107,12 +105,10 @@ onMounted(() => {
}; };
}); });
/*
挂载缩放事件
*/
const resizable = ref(windowInfo.resizable); const resizable = ref(windowInfo.resizable);
const resizemode = ref("null"); const resizemode = ref("null");
let scaleAble: ScaleElement; let scaleAble: ScaleElement;
onMounted(() => { onMounted(() => {
scaleAble = new ScaleElement( scaleAble = new ScaleElement(
resizemode, resizemode,
@ -129,6 +125,7 @@ onMounted(() => {
browserWindow.emit("resize", windowInfo.width, windowInfo.height); browserWindow.emit("resize", windowInfo.width, windowInfo.height);
}); });
}); });
function startScale(e: MouseEvent | TouchEvent, dire: string) { function startScale(e: MouseEvent | TouchEvent, dire: string) {
console.log(e); console.log(e);
if (windowInfo.disable) { if (windowInfo.disable) {
@ -146,19 +143,17 @@ function startScale(e: MouseEvent | TouchEvent, dire: string) {
onUnmounted(() => { onUnmounted(() => {
scaleAble.unMount(); scaleAble.unMount();
// dragAble.unMount();
}); });
//
const dragBorders = [ const dragBorders = [
{ type: 'r', class: 'right_border' }, { type: 'r', class: 'right_border', cursorClass: 'ew-resize' },
{ type: 'b', class: 'bottom_border' }, { type: 'b', class: 'bottom_border', cursorClass: 'ns-resize' },
{ type: 'l', class: 'left_border' }, { type: 'l', class: 'left_border', cursorClass: 'ew-resize' },
{ type: 't', class: 'top_border' }, { type: 't', class: 'top_border', cursorClass: 'ns-resize' },
{ type: 'rb', class: 'right_bottom_border' }, { type: 'rb', class: 'right_bottom_border', cursorClass: 'nwse-resize' },
{ type: 'lb', class: 'left_bottom_border' }, { type: 'lb', class: 'left_bottom_border', cursorClass: 'nesw-resize' },
{ type: 'lt', class: 'left_top_border' }, { type: 'lt', class: 'left_top_border', cursorClass: 'nwse-resize' },
{ type: 'rt', class: 'right_top_border' }, { type: 'rt', class: 'right_top_border', cursorClass: 'nesw-resize' },
]; ];
</script> </script>
<style> <style>

313
frontend/src/stores/chat.ts

@ -1,10 +1,33 @@
import emojiList from "@/assets/emoji.json"; import emojiList from "@/assets/emoji.json";
import { fetchGet, fetchPost, getSystemConfig } from '@/system/config'; import { fetchPost, getSystemConfig } from '@/system/config';
import { notifyError } from "@/util/msg"; import { notifyError } from "@/util/msg";
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { db } from "./db"; import { db } from "./db";
import { useMessageStore } from "./message"; import { useMessageStore } from "./message";
interface ChatMessage {
type: string;
createdAt: number;
content: any;
targetUserId: any;
previewType: 0 | 1; // 消息类型,0表示正常消息,1表示撤回消息
previewMessage: any;
isMe: boolean;
isRead: boolean;
userInfo: {
id: any;
username: any;
avatar: any;
};
}
// 发起群聊对话框显示
const groupChatDialogVisible = ref(false);
// 设置发起群聊对话框状态
const setGroupChatDialogVisible = (visible: boolean) => {
groupChatDialogVisible.value = visible;
};
export const useChatStore = defineStore('chatStore', () => { export const useChatStore = defineStore('chatStore', () => {
// 用户列表 // 用户列表
const userList: any = ref([]); const userList: any = ref([]);
@ -29,10 +52,19 @@ export const useChatStore = defineStore('chatStore', () => {
previewType: 1, previewType: 1,
previewMessage: "测试消息", previewMessage: "测试消息",
}, },
{
id: 3,
nickname: '朋友2',
avatar: '/logo.png',
previewTimeFormat: "昨天",
previewType: 1,
previewMessage: "测试消息",
},
]); ]);
// 模拟数据 - 聊天消息列表 // 模拟数据 - 聊天消息列表
const chatHistory = ref([]); const chatHistory = ref<ChatMessage[]>([]);
// 群组数据 // 群组数据
const groupList = ref([ const groupList = ref([
@ -82,10 +114,8 @@ export const useChatStore = defineStore('chatStore', () => {
visible: false, visible: false,
chatMessageId: 0, chatMessageId: 0,
list: [ list: [
{ { id: 1, label: '复制' },
id: 2, { id: 2, label: '删除' },
label: '撤回',
}
], ],
x: 0, x: 0,
y: 0 y: 0
@ -96,6 +126,10 @@ export const useChatStore = defineStore('chatStore', () => {
config.userInfo.avatar = '/logo.png'; config.userInfo.avatar = '/logo.png';
} }
userInfo.value = config.userInfo; userInfo.value = config.userInfo;
getUserList()
initUserList()
initOnlineUserList()
console.log(userList.value);
}; };
const setCurrentNavId = (id: number) => { const setCurrentNavId = (id: number) => {
@ -104,7 +138,7 @@ export const useChatStore = defineStore('chatStore', () => {
const sendMessage = async () => { const sendMessage = async () => {
const chatSendUrl = apiUrl + '/chat/send'; const chatSendUrl = apiUrl + '/chat/send';
const messageHistory = { const messageHistory: ChatMessage = {
type: 'text', type: 'text',
createdAt: Date.now(), createdAt: Date.now(),
content: message.value, content: message.value,
@ -165,7 +199,7 @@ export const useChatStore = defineStore('chatStore', () => {
} }
} else { } else {
const targetUser = await db.getOne('workbenchusers', id); const targetUser = await db.getOne('workbenchusers', id);
const lastMessage = messageHistory; const lastMessage: any = messageStore;
const targetUserInfo = { const targetUserInfo = {
id: targetUser.id, id: targetUser.id,
@ -177,7 +211,7 @@ export const useChatStore = defineStore('chatStore', () => {
const now = new Date(); const now = new Date();
const createdAt = new Date(lastMessage.createdAt); const createdAt = new Date(lastMessage.createdAt);
const diffTime = Math.abs(now.getTime() - createdAt.getTime()); const diffTime = Math.abs(now.getTime() - createdAt.getTime());
console.log(diffTime);
// 根据时间差格式化时间 // 根据时间差格式化时间
const previewTimeFormat = formatTime(Date.now()); const previewTimeFormat = formatTime(Date.now());
@ -220,40 +254,192 @@ export const useChatStore = defineStore('chatStore', () => {
message.value = ''; message.value = '';
}; };
const initSSE = async () => {
console.log('initSSE');
const source = new EventSource(`${apiUrl}/chat/message`);
console.log(source); const setScrollToBottom = async () => {
source.onmessage = function (event) { // await nextTick(); // 确保 DOM 已经更新完毕
const data = JSON.parse(event.data);
console.log(data); // // 检查 innerRef 是否存在
messageStore.handleMessage(data); // if (!innerRef.value) {
}; // console.warn('innerRef is not defined.');
// return;
// }
// // 设置滚动条到底部
// const max = innerRef.value.clientHeight;
// if (scrollbarRef.value) {
// scrollbarRef.value.setScrollTop(max);
// } else {
// console.warn('scrollbarRef is not defined.');
// }
};
source.onerror = function (event) { const handleUserData = async (data: any[]) => {
console.error('EventSource error:', event); ;
};
// 创建一个用户数组,将所有在线的用户提取出来
const users: any[] = [];
// 遍历每个数据项
data.forEach((item: any) => {
if (item.id && item.login_ip) {
users.push({
id: item.id,
ip: item.login_ip,
avatar: item.avatar,
username: item.username,
nickname: item.nickname
});
}
});
console.log(users);
// 将提取到的用户数据传递给 setUserList
if (users.length > 0) {
await setUserList(users);
}
}; };
const setScrollToBottom = async () => {
await nextTick(); // 确保 DOM 已经更新完毕
// 检查 innerRef 是否存在 const setUserList = async (data: any[]) => {
if (!innerRef.value) { if (data.length < 1) {
console.warn('innerRef is not defined.');
return; return;
} }
// 设置滚动条到底部 // 从当前用户列表中获取已有用户的 IP 和完整用户映射
const max = innerRef.value.clientHeight; const existingIps = new Set(userList.value.map((d: any) => d.ip));
if (scrollbarRef.value) { const userMap = new Map(
scrollbarRef.value.setScrollTop(max); userList.value.map((user: any) => [user.ip, user])
} else { );
console.warn('scrollbarRef is not defined.');
const updates: any[] = [];
const newEntries: any[] = [];
// 遍历传入的 data,每个用户根据是否存在来更新或添加
data.forEach((d: any) => {
const existingUser = userMap.get(d.ip);
if (existingUser && existingIps.has(d.ip)) {
// 若用户已存在,添加到更新列表
updates.push({
key: existingUser.id,
changes: {
isOnline: true,
nickname: d.nickname,
username: d.usernmae,
updatedAt: Date.now()
}
});
} else {
// 若用户不存在,添加到新条目列表
newEntries.push({
id: d.id,
ip: d.ip,
isOnline: true,
nickname: d.nickname,
username: d.usernmae,
createdAt: Date.now(),
updatedAt: Date.now()
});
}
});
console.log(updates);
console.log(newEntries);
// 批量更新和添加用户数据
if (updates.length > 0) {
await db.table('workbenchusers').bulkUpdate(updates);
}
if (newEntries.length > 0) {
await db.table('workbenchusers').bulkPut(newEntries);
}
// 刷新用户列表
await getUserList();
};
const getUserList = async () => {
try {
// 从数据库中获取所有用户信息
const list = await db.getAll("workbenchusers");
// 创建一个 Map,用于存储每个用户的唯一 ID 地址
let uniqueIdMap = new Map<string, any>();
// 遍历用户列表,将每个用户添加到 Map 中(基于 ID 去重)
list.forEach((item: any) => {
uniqueIdMap.set(item.id, item); // 使用 ID 作为键,用户对象作为值
});
// 将 Map 的值转换为数组(去重后的用户列表)
const uniqueIdList = Array.from(uniqueIdMap.values());
// 按照 updatedAt 时间进行升序排序
uniqueIdList.sort((a: any, b: any) => a.updatedAt - b.updatedAt);
// 更新用户列表
userList.value = uniqueIdList;
} catch (error) {
console.error("获取用户列表失败:", error);
}
};
// 初始化统一用户列表状态
const initUserList = async () => {
// 检查用户列表是否为空
if (userList.value.length > 0) {
// 收集需要更新的用户数据
const updates = userList.value
.filter((d: any) => d.isOnline) // 过滤出在线的用户
.map((d: any) => ({
key: d.id,
changes: {
isOnline: false
}
}));
// 批量更新用户状态
if (updates.length > 0) {
await db.table('workbenchusers').bulkUpdate(updates);
}
} }
}; };
const initOnlineUserList = async () => {
const msgAll = await db.getAll('workbenchusers');
const list = msgAll.reduce((acc: any, msg: any) => {
if (!msg.isMe) {
const targetId = msg.targetId;
if (!acc[targetId]) {
acc[targetId] = { chatArr: [], readNum: 0 };
}
acc[targetId].chatArr.push(msg);
// 计算未读消息数量
if (!msg.isRead) {
acc[targetId].readNum++;
}
}
return acc;
}, {});
const res = Object.keys(list).map(targetId => {
const { chatArr, readNum } = list[targetId];
const lastMessage = chatArr[chatArr.length - 1];
if (lastMessage) {
lastMessage.readNum = readNum;
return lastMessage;
}
return null; // 防止返回空值
}).filter(Boolean); // 过滤掉空值
userList.value = res.sort((a, b) => b.createdAt - a.createdAt);
};
const changeChatList = async (id: number) => { const changeChatList = async (id: number) => {
// 设置 targetUserId // 设置 targetUserId
targetUserId.value = id; targetUserId.value = id;
@ -274,59 +460,6 @@ export const useChatStore = defineStore('chatStore', () => {
contextMenu.value.visible = false; contextMenu.value.visible = false;
}; };
const getOnlineUsers = async () => {
const res = await fetchGet(apiUrl + '/chat/online?page=1');
if (!res.ok) {
notifyError("获取在线用户失败");
return;
}
const data = await res.json();
console.log(data);
const onlineUsers = data.data.list.map((item: any) => ({
id: item.id,
username: item.username,
nickname: item.nickname,
email: item.email,
phone: item.phone,
desc: item.desc,
jobNumber: item.job_number,
workPlace: item.work_place,
hiredDate: item.hired_date,
avatar: item.avatar || '/logo.png',
isOnline: true,
ip: item.login_ip,
updatedAt: item.updated_at,
createdAt: item.add_time,
}));
// 更新或添加用户
const existingUserIds = await db.getAll('workbenchusers').then(users => users.map(user => user.id));
const newUserIds = onlineUsers.filter(user => !existingUserIds.includes(user.id)).map(user => user.id);
// 更新现有用户的状态
const updatePromises = onlineUsers.filter(user => existingUserIds.includes(user.id)).map(user => {
return db.update('workbenchusers', user.id, {
isOnline: true,
updatedAt: user.updatedAt,
ip: user.ip,
});
});
// 添加新用户
const addPromises = onlineUsers.filter((user: { id: any }) => newUserIds.includes(user.id)).map((user: any) => {
return db.addOne('workbenchusers', user);
});
await Promise.all([...updatePromises, ...addPromises]);
// 更新 userList
userList.value = await db.getAll('workbenchusers');
console.log(userList.value);
};
const showContextMenu = (event: any, id: number) => { const showContextMenu = (event: any, id: number) => {
contextMenu.value.visible = true; contextMenu.value.visible = true;
@ -354,14 +487,16 @@ export const useChatStore = defineStore('chatStore', () => {
message, message,
contextMenu, contextMenu,
activeNames, activeNames,
groupChatDialogVisible,
initChat, initChat,
initSSE, showContextMenu,
setCurrentNavId, setCurrentNavId,
sendMessage, sendMessage,
changeChatList, changeChatList,
handleContextMenu, handleContextMenu,
showContextMenu, updateConversationList,
getOnlineUsers, handleUserData,
updateConversationList initUserList,
setGroupChatDialogVisible
}; };
}); });

1
frontend/src/stores/localchat.ts

@ -26,7 +26,6 @@ export const useLocalChatStore = defineStore('localChatStore', () => {
const chatTargetIp = ref("") const chatTargetIp = ref("")
const showAddUser = ref(false) const showAddUser = ref(false)
const handlerMessage = (data : any) => { const handlerMessage = (data : any) => {
//console.log(data)
if(data.onlines){ if(data.onlines){
const ips = [] const ips = []
for(let ip in data.onlines){ for(let ip in data.onlines){

317
frontend/src/stores/upgrade.ts

@ -1,162 +1,185 @@
import { t } from '@/i18n';
import { getSystemConfig, getUrl, parseJson, setSystemKey } from '@/system/config';
import { RestartApp } from '@/util/goutil';
import { ElMessage } from 'element-plus';
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { ref } from "vue"; import { ref } from "vue";
import { setSystemKey, parseJson, getSystemConfig } from '@/system/config' import { useChatStore } from "./chat";
import { RestartApp } from '@/util/goutil';
import { ElMessage } from 'element-plus'
import { t } from '@/i18n';
import { useLocalChatStore } from "./localchat"; import { useLocalChatStore } from "./localchat";
export const useUpgradeStore = defineStore('upgradeStore', () => { export const useUpgradeStore = defineStore('upgradeStore', () => {
const hasUpgrade = ref(false); const hasUpgrade = ref(false);
const hasNotice = ref(false); const hasNotice = ref(false);
const hasAd = ref(false); const hasAd = ref(false);
const updateUrl = ref(''); const updateUrl = ref('');
const versionTag = ref('') const versionTag = ref('')
const upgradeDesc = ref('') const upgradeDesc = ref('')
const currentVersion = ref('') const currentVersion = ref('')
const progress = ref(0) const progress = ref(0)
const noticeList:any = ref([]) const noticeList: any = ref([])
const adList:any = ref([]) const adList: any = ref([])
const localChatStore = useLocalChatStore() const localChatStore = useLocalChatStore()
function compareVersions(version1:string, version2:string) { const chatChatStore = useChatStore()
// 将版本号字符串按"."分割成数组 function compareVersions(version1: string, version2: string) {
const parts1 = version1.split('.').map(Number); // 将版本号字符串按"."分割成数组
const parts2 = version2.split('.').map(Number); const parts1 = version1.split('.').map(Number);
const parts2 = version2.split('.').map(Number);
// 确保两个数组长度相同
const maxLength = Math.max(parts1.length, parts2.length); // 确保两个数组长度相同
while (parts1.length < maxLength) parts1.push(0); const maxLength = Math.max(parts1.length, parts2.length);
while (parts2.length < maxLength) parts2.push(0); while (parts1.length < maxLength) parts1.push(0);
while (parts2.length < maxLength) parts2.push(0);
// 比较每个部分
for (let i = 0; i < maxLength; i++) { // 比较每个部分
if (parts1[i] > parts2[i]) return 1; for (let i = 0; i < maxLength; i++) {
if (parts1[i] < parts2[i]) return -1; if (parts1[i] > parts2[i]) return 1;
} if (parts1[i] < parts2[i]) return -1;
// 如果所有部分都相等,则返回0
return 0;
} }
function systemMessage(){
const config = getSystemConfig();
const source = new EventSource(`${config.apiUrl}/system/message`);
source.onmessage = function(event) { // 如果所有部分都相等,则返回0
const data = JSON.parse(event.data); return 0;
//console.log(data) }
handleMessage(data); function systemMessage() {
}; const config = getSystemConfig();
source.onerror = function(event) { const source = new EventSource(`${config.apiUrl}/system/message`);
console.error('EventSource error:', event);
}; source.onmessage = function (event) {
const data = JSON.parse(event.data);
//console.log(data)
handleMessage(data);
};
source.onerror = function (event) {
console.error('EventSource error:', event);
};
}
// 获取在线消息
function onlineMessage() {
const url = getUrl('/chat/message', false)
const source = new EventSource(url);
source.onmessage = function (event) {
const data = JSON.parse(event.data);
handleMessage(data);
};
source.onerror = function (event) {
console.error('EventSource error:', event);
};
}
async function handleMessage(message: any) {
switch (message.type) {
case 'update':
checkUpdate(message.data)
break;
case 'localchat':
localChatStore.handlerMessage(message.data)
break;
case 'online':
chatChatStore.handleUserData(message.data)
break;
default:
console.warn('Unknown message type:', message.type);
} }
async function handleMessage(message:any) { }
switch (message.type) { async function checkUpdate(res: any) {
case 'update': //console.log(res)
checkUpdate(message.data) if (!res) return
break; const config = getSystemConfig();
case 'localchat': if (!config.account.ad) return;
localChatStore.handlerMessage(message.data) currentVersion.value = config.version;
break; let bottomList: any = []
default: let centerList: any = []
console.warn('Unknown message type:', message.type); if (res.adlist && res.adlist.length > 0) {
} bottomList = res.adlist[0]['bottom']
centerList = res.adlist[0]['center']
} }
async function checkUpdate(res:any) { if (bottomList && bottomList.length > 0) {
//console.log(res) hasNotice.value = true
if(!res)return noticeList.value = [...noticeList.value, ...changeUrl(bottomList)]
const config = getSystemConfig(); }
if(!config.account.ad)return; //console.log(noticeList)
currentVersion.value = config.version; //console.log(centerList)
let bottomList:any = [] if (centerList && centerList.length > 0) {
let centerList:any = [] hasAd.value = true
if (res.adlist && res.adlist.length > 0) { adList.value = [...adList.value, ...changeUrl(centerList)]
bottomList = res.adlist[0]['bottom'] }
centerList = res.adlist[0]['center'] //console.log(adList.value)
}
if(bottomList && bottomList.length > 0){
hasNotice.value = true
noticeList.value = [...noticeList.value, ...changeUrl(bottomList)]
}
//console.log(noticeList)
//console.log(centerList)
if(centerList && centerList.length > 0){
hasAd.value = true
adList.value = [...adList.value, ...changeUrl(centerList)]
}
//console.log(adList.value)
if(!res.version || res.version == ""){ if (!res.version || res.version == "") {
return return
} }
versionTag.value = res.version versionTag.value = res.version
if (compareVersions(versionTag.value, config.version) > 0) { if (compareVersions(versionTag.value, config.version) > 0) {
upgradeDesc.value = res.desc ?? t('upgrade.msg') upgradeDesc.value = res.desc ?? t('upgrade.msg')
hasUpgrade.value = true hasUpgrade.value = true
updateUrl.value = res.url updateUrl.value = res.url
}
} }
function changeUrl(list : any){ }
list.forEach((item:any) => { function changeUrl(list: any) {
if(item.img && item.img.indexOf('http') == -1){ list.forEach((item: any) => {
item.img = `https://godoos.com${item.img}` if (item.img && item.img.indexOf('http') == -1) {
item.img = `https://godoos.com${item.img}`
}
}); }
return list });
return list
}
async function update() {
const config = getSystemConfig();
const upUrl = `${config.apiUrl}/system/update?url=${updateUrl.value}`
const upRes = await fetch(upUrl)
if (!upRes.ok) return;
const reader: any = upRes.body?.getReader();
if (!reader) {
ElMessage({
type: 'error',
message: "the system has not stream!"
})
} }
async function update() { while (true) {
const config = getSystemConfig(); const { done, value } = await reader.read();
const upUrl = `${config.apiUrl}/system/update?url=${updateUrl.value}` if (done) {
reader.releaseLock();
const upRes = await fetch(upUrl) break;
if (!upRes.ok) return; }
const reader: any = upRes.body?.getReader(); const rawjson = new TextDecoder().decode(value);
if (!reader) { const json = parseJson(rawjson);
ElMessage({ //console.log(json)
type: 'error', if (json) {
message: "the system has not stream!" if (json.progress) {
}) progress.value = json.progress
} }
while (true) { if (json.updateCompleted) {
const { done, value } = await reader.read(); hasUpgrade.value = false
if (done) { progress.value = 0
reader.releaseLock(); ElMessage({
break; type: 'success',
} message: "update completed!"
const rawjson = new TextDecoder().decode(value); })
const json = parseJson(rawjson); setSystemKey('version', versionTag.value)
//console.log(json) currentVersion.value = versionTag.value
if (json) { RestartApp()
if (json.progress) { break;
progress.value = json.progress
}
if (json.updateCompleted) {
hasUpgrade.value = false
progress.value = 0
ElMessage({
type: 'success',
message: "update completed!"
})
setSystemKey('version', versionTag.value)
currentVersion.value = versionTag.value
RestartApp()
break;
}
}
} }
}
} }
return { }
hasUpgrade, return {
hasNotice, hasUpgrade,
hasAd, hasNotice,
versionTag, hasAd,
upgradeDesc, versionTag,
updateUrl, upgradeDesc,
noticeList, updateUrl,
adList, noticeList,
progress, adList,
checkUpdate, progress,
systemMessage, checkUpdate,
update systemMessage,
} onlineMessage,
update
}
}) })

66
frontend/src/system/index.ts

@ -1,6 +1,10 @@
import { markRaw, nextTick } from 'vue';
import { version } from '../../package.json';
import { useOsFile } from './core/FileOs';
import { OsFileSystem } from './core/FileSystem';
import { Eventer, initEventer, initEventListener } from './event';
import { initRootState, RootState } from './root'; import { initRootState, RootState } from './root';
import { SystemStateEnum } from './type/enum'; import { SystemStateEnum } from './type/enum';
import { markRaw, nextTick } from 'vue';
import { import {
Saveablekey, Saveablekey,
Setting, Setting,
@ -8,26 +12,22 @@ import {
SystemOptionsCertainly, SystemOptionsCertainly,
WinAppOptions, WinAppOptions,
} from './type/type'; } from './type/type';
import { initEventer, Eventer, initEventListener } from './event';
import { OsFileSystem } from './core/FileSystem';
import { useOsFile } from './core/FileOs';
import { version } from '../../package.json';
import { BrowserWindow, BrowserWindowOption } from './window/BrowserWindow'; import { BrowserWindow, BrowserWindowOption } from './window/BrowserWindow';
import { useMessageStore } from '@/stores/message';
import { useUpgradeStore } from '@/stores/upgrade';
import { RestartApp } from '@/util/goutil';
import { notifyError } from '@/util/msg';
import { isShareFile } from '@/util/sharePath';
import { pick } from '../util/modash';
import { clearSystemConfig, fetchGet, getClientId, getFileUrl, getSystemConfig, getSystemKey, setSystemConfig, setSystemKey } from './config';
import { OsFileInterface } from './core/FIleInterface';
import { extname } from './core/Path'; import { extname } from './core/Path';
import { initBuiltinApp, initBuiltinFileOpener } from './initBuiltin'; import { initBuiltinApp, initBuiltinFileOpener } from './initBuiltin';
import { defaultConfig } from './initConfig'; import { defaultConfig } from './initConfig';
import { OsFileInterface } from './core/FIleInterface'; import { Tray, TrayOptions } from './menu/Tary';
import { Notify, NotifyConstructorOptions } from './notification/Notification'; import { Notify, NotifyConstructorOptions } from './notification/Notification';
import { Dialog } from './window/Dialog'; import { Dialog } from './window/Dialog';
import { pick } from '../util/modash';
import { Tray, TrayOptions } from './menu/Tary';
import { getSystemConfig, getSystemKey, setSystemKey, setSystemConfig, clearSystemConfig, getFileUrl, fetchGet, getClientId } from './config'
import { useUpgradeStore } from '@/stores/upgrade';
import { useMessageStore } from '@/stores/message';
import { RestartApp } from '@/util/goutil';
import { notifyError } from '@/util/msg';
import { isShareFile } from '@/util/sharePath';
export type OsPlugin = (system: System) => void; export type OsPlugin = (system: System) => void;
export type FileOpener = { export type FileOpener = {
@ -113,17 +113,19 @@ export class System {
} }
private checkMessages() { private checkMessages() {
const config = getSystemConfig(); const config = getSystemConfig();
const upgradeStore = useUpgradeStore();
if (config.userType == 'member') { if (config.userType == 'member') {
setTimeout(() => { setTimeout(() => {
const messageStore = useMessageStore(); // const messageStore = useMessageStore();
messageStore.systemMessage() // messageStore.systemMessage()
upgradeStore.onlineMessage();
}, 3000); }, 3000);
} }
setTimeout(() => { setTimeout(() => {
const upgradeStore = useUpgradeStore(); // upgradeStore.systemMessage()
upgradeStore.systemMessage()
}, 6000); }, 6000);
} }
/** /**
* @description: * @description:
@ -431,6 +433,7 @@ export class System {
if (isShareFile(path)) { if (isShareFile(path)) {
const arr = path.split('/') const arr = path.split('/')
const fileContent = await this.fs.readShareFile(path) const fileContent = await this.fs.readShareFile(path)
<<<<<<< HEAD
// console.log('阅读:', fileContent); // console.log('阅读:', fileContent);
if (fileContent !== false) { if (fileContent !== false) {
const fileName = extname(arr[arr.length-1] || '') || 'link' const fileName = extname(arr[arr.length-1] || '') || 'link'
@ -438,6 +441,12 @@ export class System {
.get(fileName) .get(fileName)
?.func.call(this, path, fileContent || ''); ?.func.call(this, path, fileContent || '');
} }
=======
const fileName = extname(arr[arr.length - 1] || '') || 'link'
this._flieOpenerMap
.get(fileName)
?.func.call(this, path, fileContent || '');
>>>>>>> 89f84204e655e3df0824fe91c9b17bc8a9d6ad87
} else { } else {
const fileStat = await this.fs.stat(path) const fileStat = await this.fs.stat(path)
if (!fileStat) { if (!fileStat) {
@ -517,16 +526,17 @@ export function useSystem() {
return System.GLOBAL_SYSTEM!; return System.GLOBAL_SYSTEM!;
} }
export * from './core/Path'; export { t } from '../i18n';
export { dealIcon } from '../util/Icon';
export type { OsFileInterface } from './core/FIleInterface';
export * from './core/FileSystem'; export * from './core/FileSystem';
export { BrowserWindow } from './window/BrowserWindow'; export * from './core/Path';
export { Menu } from './menu/Menu';
export { MenuItem } from './menu/MenuItem';
export { Tray } from './menu/Tary';
export { Notify } from './notification/Notification'; export { Notify } from './notification/Notification';
export { Dialog } from './window/Dialog';
export type { SystemOptions, WinApp } from './type/type'; export type { SystemOptions, WinApp } from './type/type';
export { BrowserWindow } from './window/BrowserWindow';
export { Dialog } from './window/Dialog';
export { vDragable } from './window/MakeDragable'; export { vDragable } from './window/MakeDragable';
export type { OsFileInterface } from './core/FIleInterface';
export { t } from '../i18n';
export { Tray } from './menu/Tary';
export { dealIcon } from '../util/Icon';
export { Menu } from './menu/Menu';
export { MenuItem } from './menu/MenuItem';

1
frontend/tsconfig.json

@ -1,6 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"noEmitOnError": false,
//"target": "esnext", //"target": "esnext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",

2
godo/cmd/main.go

@ -101,6 +101,8 @@ func OsStart() {
fileRouter.HandleFunc("/unzip", files.HandleUnZip).Methods(http.MethodGet) fileRouter.HandleFunc("/unzip", files.HandleUnZip).Methods(http.MethodGet)
fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet) fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet)
fileRouter.HandleFunc("/setfilepwd", files.HandleSetFilePwd).Methods(http.MethodGet) fileRouter.HandleFunc("/setfilepwd", files.HandleSetFilePwd).Methods(http.MethodGet)
fileRouter.HandleFunc("/changefilepwd", files.HandleChangeFilePwd).Methods(http.MethodGet)
fileRouter.HandleFunc("/changeisPwd", files.HandleSetIsPwd).Methods(http.MethodGet)
localchatRouter := router.PathPrefix("/localchat").Subrouter() localchatRouter := router.PathPrefix("/localchat").Subrouter()
localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost) localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost)

23
godo/files/fs.go

@ -315,9 +315,8 @@ func HandleCopyFile(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
} }
// HandleWriteFile writes content to a file // 带加密写
func HandleWriteFile(w http.ResponseWriter, r *http.Request) { func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
// basepath = "/Users/sujia/.godoos/os"
filePath := r.URL.Query().Get("filePath") filePath := r.URL.Query().Get("filePath")
basePath, err := libs.GetOsDir() basePath, err := libs.GetOsDir()
if err != nil { if err != nil {
@ -346,12 +345,27 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
} }
defer file.Close() defer file.Close()
// 内容为空直接返回,不为空则加密 // 内容为空直接返回
if len(filedata) == 0 { if len(filedata) == 0 {
CheckAddDesktop(filePath) CheckAddDesktop(filePath)
libs.SuccessMsg(w, "", "success") libs.SuccessMsg(w, "", "success")
return return
} }
// 判读是否加密
ispwd := GetPwdFlag()
// 没有加密写入明文
if ispwd == 0 {
_, err := io.Copy(file, fileContent)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
CheckAddDesktop(filePath)
libs.SuccessMsg(w, "", "success")
return
}
// 加密 // 加密
data, err := libs.EncryptData(filedata, libs.EncryptionKey) data, err := libs.EncryptData(filedata, libs.EncryptionKey)
if err != nil { if err != nil {
@ -365,8 +379,7 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
} }
// 判断下是否添加到桌面上 // 判断下是否添加到桌面上
CheckAddDesktop(filePath) CheckAddDesktop(filePath)
res := libs.APIResponse{Message: fmt.Sprintf("File '%s' successfully written.", filePath)} libs.SuccessMsg(w, "", "success")
json.NewEncoder(w).Encode(res)
} }
// HandleAppendFile appends content to a file // HandleAppendFile appends content to a file

16
godo/files/os.go

@ -339,7 +339,7 @@ func CheckDeleteDesktop(filePath string) error {
// 校验文件密码 // 校验文件密码
func CheckFilePwd(fpwd, salt string) bool { func CheckFilePwd(fpwd, salt string) bool {
pwd := libs.HashPassword(fpwd, salt) pwd := libs.HashPassword(fpwd, salt)
oldpwd, err := libs.GetConfig("filepwd") oldpwd, err := libs.GetConfig("filePwd")
if !err { if !err {
return false return false
} }
@ -365,3 +365,17 @@ func GetSalt(r *http.Request) string {
return salt return salt
} }
} }
// 获取密码标识位,没有添加上
func GetPwdFlag() int {
isPwd, has := libs.GetConfig("isPwd")
if !has {
req := libs.ReqBody{
Name: "isPwd",
Value: 0,
}
libs.SetConfig(req)
libs.SaveConfig()
}
return isPwd.(int)
}

56
godo/files/pwdfile.go

@ -5,33 +5,24 @@ import (
"encoding/json" "encoding/json"
"godo/libs" "godo/libs"
"net/http" "net/http"
"strconv"
"strings" "strings"
) )
// 加密读 // 加密读
func HandleReadFile(w http.ResponseWriter, r *http.Request) { func HandleReadFile(w http.ResponseWriter, r *http.Request) {
// 初始值
path := r.URL.Query().Get("path") path := r.URL.Query().Get("path")
fPwd := r.Header.Get("fPwd") fPwd := r.Header.Get("fPwd")
hasPwd := IsHavePwd(fPwd)
// 获取salt值
salt := GetSalt(r) salt := GetSalt(r)
hasPwd := GetPwdFlag()
// 校验文件路径 // 校验文件路径
if err := validateFilePath(path); err != nil { if err := validateFilePath(path); err != nil {
libs.HTTPError(w, http.StatusBadRequest, err.Error()) libs.HTTPError(w, http.StatusBadRequest, err.Error())
return return
} }
// 有密码校验密码
if hasPwd {
if !CheckFilePwd(fPwd, salt) {
libs.HTTPError(w, http.StatusBadRequest, "密码错误")
return
}
}
// 获取文件路径 // 获取文件路径
basePath, err := libs.GetOsDir() basePath, err := libs.GetOsDir()
if err != nil { if err != nil {
@ -45,14 +36,31 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
return return
} }
// 解密 // 没有加密 base64明文传输
data, err := libs.DecryptData(fileContent, libs.EncryptionKey) if hasPwd == 0 {
data := string(fileContent)
if !strings.HasPrefix(data, "link::") {
data = base64.StdEncoding.EncodeToString(fileContent)
resp := libs.APIResponse{Message: "success", Data: data}
json.NewEncoder(w).Encode(resp)
return
}
}
// 有加密,先校验密码,再解密
if !CheckFilePwd(fPwd, salt) {
libs.HTTPError(w, http.StatusBadRequest, "密码错误")
return
}
var data []byte
fileContent, err = libs.DecryptData(fileContent, libs.EncryptionKey)
if err != nil { if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error()) libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return return
} }
content := string(data) content := string(fileContent)
// 检查文件内容是否以"link::"开头 // 检查文件内容是否以"link::"开头
if !strings.HasPrefix(content, "link::") { if !strings.HasPrefix(content, "link::") {
content = base64.StdEncoding.EncodeToString(data) content = base64.StdEncoding.EncodeToString(data)
@ -60,7 +68,6 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
// 初始响应 // 初始响应
res := libs.APIResponse{Code: 0, Message: "success", Data: content} res := libs.APIResponse{Code: 0, Message: "success", Data: content}
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
} }
@ -68,6 +75,7 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
func HandleSetFilePwd(w http.ResponseWriter, r *http.Request) { func HandleSetFilePwd(w http.ResponseWriter, r *http.Request) {
fPwd := r.Header.Get("filepPwd") fPwd := r.Header.Get("filepPwd")
salt := r.Header.Get("salt") salt := r.Header.Get("salt")
// 服务端再hash加密 // 服务端再hash加密
hashPwd := libs.HashPassword(fPwd, salt) hashPwd := libs.HashPassword(fPwd, salt)
@ -104,3 +112,17 @@ func HandleChangeFilePwd(w http.ResponseWriter, r *http.Request) {
libs.SetConfig(pwdReq) libs.SetConfig(pwdReq)
libs.SuccessMsg(w, "success", "The file password change success!") libs.SuccessMsg(w, "success", "The file password change success!")
} }
// 更改加密状态
func HandleSetIsPwd(w http.ResponseWriter, r *http.Request) {
isPwd := r.URL.Query().Get("ispwd")
// 0非加密机器 1加密机器
isPwdValue, _ := strconv.Atoi(isPwd)
pwdReq := libs.ReqBody{
Name: "isPwd",
Value: isPwdValue,
}
libs.SetConfig(pwdReq)
libs.SaveConfig()
libs.SuccessMsg(w, "success", "")
}

Loading…
Cancel
Save