Browse Source

change readme

master
godo 10 months ago
parent
commit
336c57e042
  1. 14
      README.en.md
  2. 30
      README.md
  3. 239
      docs/Api.md
  4. 15
      docs/Faq.md
  5. 0
      docs/Guide.md
  6. 241
      docs/file.md
  7. 339
      frontend/src/assets/store.scss
  8. 85
      frontend/src/components/store/AddApp.vue
  9. 10
      frontend/src/components/store/AppItem.vue
  10. 477
      frontend/src/components/store/Store.vue
  11. 3
      frontend/src/i18n/lang/en.json
  12. 3
      frontend/src/i18n/lang/zh.json
  13. 9
      godo/cmd/main.go
  14. 1
      main.go

14
README.en.md

@ -11,6 +11,8 @@ An efficient intranet office platform that includes various tools such as Word,
English | [简体中文](README.md)
[FAQ](./docs/Faq.md) | [API](./docs/Api.md)
### Install
[![Windows][Windows-image]][Windows-url]
@ -38,12 +40,12 @@ English | [简体中文](README.md)
</div>
## Highlights
- No need for internet connection, fully open source
- Zero configuration, no registration required, download and use immediately
- Zero pollution, no plugin dependency
- Small in size, packaged for only 61M, it does include all the office suites
- Unlimited scalability, supports custom applications
- Golang develops backend with low resource consumption and high performance
- ***No need for internet connection, fully open source***
- ***Zero configuration, no registration required, download and use immediately***
- ***Zero pollution, no plugin dependency***
- ***Small in size, packaged for only 61M, it does include all the office suites***
- ***Unlimited scalability, supports custom applications***
- ***Golang develops backend with low resource consumption and high performance***
## Function Description

30
README.md

@ -11,6 +11,10 @@
[English](README.en.md) | 简体中文
[FAQ](./docs/Faq.md) | [API](./docs/Api.md)
### 安装
[![Windows][Windows-image]][Windows-url]
@ -38,14 +42,24 @@
</div>
## 亮点
- 无需联网使用,全开源
- 零配置,无需注册,下载即用
- 零污染,无插件依赖
- 精小,打包后仅61M,确包含了所有的办公套件
- 可无限扩展,支持自定义应用
- golang开发后端,低资源消耗和高性能
## 功能说明
- ***无需联网使用,全开源***
- ***零配置,无需注册,下载即用***
- ***零污染,无插件依赖***
- ***精小,打包后仅61M,确包含了所有的办公套件***
- ***可无限扩展,支持自定义应用***
- ***golang开发后端,低资源消耗和高性能***
## 开发进程
- 2024年7月,发布v1.0.0版本,发布后,项目进入第二阶段。
## 第二阶段目标
- [x] 完善的应用商店体系
- [x] 完善开发文档
- [x] 支持远程存储
- [x] 完成企业客户端
## 功能说明和预览
### 一、系统桌面
- 精仿windows风格

239
docs/Api.md

@ -0,0 +1,239 @@
## API
- 系统接口地址: http://localhost:56780
### 读取目录
#### HTTP 方法
`GET`
#### 路径
`/files/read`
#### 请求参数
- **Query 参数**: `path` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 目录内容列表
---
### 获取文件或目录状态
#### HTTP 方法
`GET`
#### 路径
`/files/stat`
#### 请求参数
- **Query 参数**: `path` (文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 文件或目录的状态信息
---
### 更改文件权限
#### HTTP 方法
`POST`
#### 路径
`/files/chmod`
#### 请求体
- **Content-Type**: `application/json`
- **Body**: `{ "path": "string", "mode": "string" }`
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 检查文件或目录是否存在
#### HTTP 方法
`GET`
#### 路径
`/files/exists`
#### 请求参数
- **Query 参数**: `path` (文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 存在性检查结果
---
### 读取文件内容
#### HTTP 方法
`GET`
#### 路径
`/files/readfile`
#### 请求参数
- **Query 参数**: `path` (文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 文件内容
---
### 删除文件
#### HTTP 方法
`GET`
#### 路径
`/files/unlink`
#### 请求参数
- **Query 参数**: `path` (文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 清空文件系统
#### HTTP 方法
`GET`
#### 路径
`/files/clear`
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 重命名文件或目录
#### HTTP 方法
`GET`
#### 路径
`/files/rename`
#### 请求参数
- **Query 参数**: `oldPath` (原文件或目录路径), `newPath` (新文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 创建目录
#### HTTP 方法
`POST`
#### 路径
`/files/mkdir`
#### 请求参数
- **Query 参数**: `dirPath` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 删除目录
#### HTTP 方法
`GET`
#### 路径
`/files/rmdir`
#### 请求参数
- **Query 参数**: `dirPath` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 复制文件
#### HTTP 方法
`GET`
#### 路径
`/files/copyfile`
#### 请求参数
- **Query 参数**: `srcPath` (源文件路径), `dstPath` (目标文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 写入文件
#### HTTP 方法
`POST`
#### 路径
`/files/writefile`
#### 请求参数
- **Query 参数**: `filePath` (文件路径)
#### 请求体
- **Content-Type**: `multipart/form-data`
- **Body**: 包含 `content` 的表单数据
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 追加文件内容
#### HTTP 方法
`POST`
#### 路径
`/files/appendfile`
#### 请求参数
- **Query 参数**: `filePath` (文件路径)
#### 请求体
- **Content-Type**: `multipart/form-data`
- **Body**: 包含 `content` 的表单数据
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 文件系统事件监听
#### 功能
监听文件系统变化事件
#### 参数
- **path** (监听的文件或目录路径)
- **callback** (事件回调函数)
- **errback** (错误回调函数)

15
docs/Faq.md

@ -0,0 +1,15 @@
## 是否支持浏览器访问?
- 支持。
```
cd frontend
pnpm build
```
然后复制打包后的dist目录到运行程序的根目录,然后重启程序。访问地址为http://localhost:56780/
## 为什么找不到本地文件?
程序默认为浏览器存储。进入系统设置,修改存储方式为本地存储。
## 是否支持切换存储目录?
-支持。
进入系统设置页面,修改存储目录即可。修改后程序会重启一次。

0
docs/Guide.md

241
docs/file.md

@ -1,241 +0,0 @@
---
title: 文件系统接口
icon: circle-info
---
### 读取目录
#### HTTP 方法
`GET`
#### 路径
`/read`
#### 请求参数
- **Query 参数**: `path` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 目录内容列表
---
### 获取文件或目录状态
#### HTTP 方法
`GET`
#### 路径
`/stat`
#### 请求参数
- **Query 参数**: `path` (文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 文件或目录的状态信息
---
### 更改文件权限
#### HTTP 方法
`POST`
#### 路径
`/chmod`
#### 请求体
- **Content-Type**: `application/json`
- **Body**: `{ "path": "string", "mode": "string" }`
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 检查文件或目录是否存在
#### HTTP 方法
`GET`
#### 路径
`/exists`
#### 请求参数
- **Query 参数**: `path` (文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 存在性检查结果
---
### 读取文件内容
#### HTTP 方法
`GET`
#### 路径
`/readfile`
#### 请求参数
- **Query 参数**: `path` (文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 文件内容
---
### 删除文件
#### HTTP 方法
`GET`
#### 路径
`/unlink`
#### 请求参数
- **Query 参数**: `path` (文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 清空文件系统
#### HTTP 方法
`GET`
#### 路径
`/clear`
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 重命名文件或目录
#### HTTP 方法
`GET`
#### 路径
`/rename`
#### 请求参数
- **Query 参数**: `oldPath` (原文件或目录路径), `newPath` (新文件或目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 创建目录
#### HTTP 方法
`POST`
#### 路径
`/mkdir`
#### 请求参数
- **Query 参数**: `dirPath` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 删除目录
#### HTTP 方法
`GET`
#### 路径
`/rmdir`
#### 请求参数
- **Query 参数**: `dirPath` (目录路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 复制文件
#### HTTP 方法
`GET`
#### 路径
`/copyfile`
#### 请求参数
- **Query 参数**: `srcPath` (源文件路径), `dstPath` (目标文件路径)
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 写入文件
#### HTTP 方法
`POST`
#### 路径
`/writefile`
#### 请求参数
- **Query 参数**: `filePath` (文件路径)
#### 请求体
- **Content-Type**: `multipart/form-data`
- **Body**: 包含 `content` 的表单数据
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 追加文件内容
#### HTTP 方法
`POST`
#### 路径
`/appendfile`
#### 请求参数
- **Query 参数**: `filePath` (文件路径)
#### 请求体
- **Content-Type**: `multipart/form-data`
- **Body**: 包含 `content` 的表单数据
#### 响应
- **Content-Type**: `application/json`
- **响应体**: 操作结果
---
### 文件系统事件监听
#### 功能
监听文件系统变化事件
#### 参数
- **path** (监听的文件或目录路径)
- **callback** (事件回调函数)
- **errback** (错误回调函数)

339
frontend/src/assets/store.scss

@ -0,0 +1,339 @@
::-webkit-scrollbar {
width: 5px;
height: 16px;
background-color: #ffffff;
}
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #ffffff;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: rgba(132, 132, 132, 0.537);
}
.outer {
display: flex;
flex-direction: column;
height: 100%;
}
.uper {
padding: 0px 20px;
font-size: 12px;
height: 40px;
line-height: 40px;
flex-shrink: 0;
color: rgb(134, 134, 134);
background-color: rgb(243, 243, 243);
}
.store-handle {
width: 100%;
height: 40px;
user-select: none;
display: flex;
flex-direction: row;
justify-content: flex-end;
position: absolute;
top: 10px;
right: 20px;
}
.main {
width: 100%;
display: flex;
flex: 1;
overflow: hidden;
background-color: rgb(243, 243, 243);
}
.left {
width: 60px;
height: auto;
flex-shrink: 0;
background-color: rgb(243, 243, 243);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.left-icon {
width: 56px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
position: relative;
cursor: pointer;
&:hover {
background-color: rgb(243, 243, 243);
}
}
.icon-derc {
height: 20px;
width: 4px;
border-radius: 4px;
background-color: #363533;
position: absolute;
left: 2px;
}
.left-icon .ep_icon {
width: 20px;
height: 20px;
color: #363533;
stroke: #363533;
}
.store {
/* width: 100%; */
flex: 1;
background-color: rgb(255, 255, 255);
border-top-left-radius: 10px;
overflow: hidden;
}
.store-top {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
overflow: hidden;
}
.left-bar {
width: 60px;
flex-shrink: 0;
}
.right-main {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.main-title {
font-size: 26px;
font-weight: bold;
font-variant: small-caps;
font-variant-ligatures: discretionary-ligatures;
margin: 8px;
margin-top: 8px;
user-select: none;
overflow: hidden;
white-space: nowrap;
}
.sub-title {
padding-top: 30px;
font-size: 20px;
font-weight: bold;
margin: 10px;
user-select: none;
overflow: hidden;
white-space: nowrap;
}
.sub-tip {
font-size: 12px;
color: rgb(11, 31, 111);
margin-left: 10px;
user-select: none;
}
.swiper {
width: max-content;
height: 300px;
/* overflow: hidden; */
margin: 0px 10px 10px 10px;
position: relative;
}
@keyframes swiperAni {
0%,
33% {
transform: translateX(0px);
}
36%,
66% {
transform: translateX(-600px);
}
69%,
96% {
transform: translateX(-1200px);
}
100% {
transform: translateX(0px);
}
}
.swiper-inner {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
animation: swiperAni 20s ease-in-out infinite;
}
.swiper-tab {
width: 600px;
height: 300px;
background-color: rgb(243, 243, 243);
border-radius: 20px;
box-shadow: 0px 10px 20px 1px #2524241f;
}
.swiper-tab img {
width: 100%;
height: 100%;
border-radius: 20px;
object-fit: cover;
}
.swiper-txt {
position: absolute;
top: 20px;
left: 30px;
font-weight: 600;
color: white;
z-index: 10;
text-shadow: 0px 0px 5px #00000058;
}
.main-app {
width: 100%;
/* height: 100%; */
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
padding: 10px;
overflow: auto;
align-content: flex-start;
}
.store-noready {
width: 100%;
height: 100%;
background-color: #cdcdcd69;
}
#wait {
position: absolute;
left: 50%;
top: 50%;
}
.waitd {
position: absolute;
width: 10px;
height: 10px;
left: 30px;
background-color: azure;
border-radius: 50%;
transform-origin: -15px 0;
}
#wait1 {
animation: dotAni1 2s linear infinite;
}
#wait2 {
animation: dotAni2 2s linear infinite;
}
#wait3 {
animation: dotAni3 2s linear infinite;
}
#wait4 {
animation: dotAni4 2s linear infinite;
}
@keyframes dotAni1 {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni2 {
0% {
transform: rotateZ(0deg);
}
35% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni3 {
0% {
transform: rotateZ(0deg);
}
50% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni4 {
0% {
transform: rotateZ(0deg);
}
65% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}

85
frontend/src/components/store/AddApp.vue

@ -1,59 +1,66 @@
<script setup lang="ts">
import { ref } from "vue";
import { OpenDirDialog } from "@/util/goutil";
const formData = ref({
importType: 'remote'
importType: 'download',
url: '',
devPath:''
})
const addType = [
{
'name' : '远程下载',
'type' : 'remote'
'name': '远程下载',
'type': 'download'
},
{
'name' : '本地导入',
'type' : 'local'
'name': '本地导入',
'type': 'local'
},
{
'name' : '开发模式',
'type' : 'dev'
'name': '开发模式',
'type': 'dev'
}
]
function selectFile() {
OpenDirDialog().then((res: string) => {
formData.value.devPath = res;
});
}
async function addAppByDownload() {
}
async function addAppByImport() {
}
async function addAppByDev() {
}
</script>
<template>
<div class="setting">
<div class="setting-item">
<label>添加方式</label>
<el-select v-model="formData.importType" style="width: 180px;">
<el-option
v-for="item in addType"
:key="item.type"
:label="item.name"
:value="item.type"
/>
<el-form label-width="auto" style="max-width: 600px;padding-left: 30px;">
<el-form-item label="添加方式">
<el-select v-model="formData.importType" style="width: 280px;">
<el-option v-for="item in addType" :key="item.type" :label="item.name" :value="item.type" />
</el-select>
</div>
</div>
</el-form-item>
<template v-if="formData.importType == 'download'">
<el-form-item label="下载地址">
<el-input v-model="formData.url" style="width: 280px;" />
</el-form-item>
<el-button type="primary" @click="addAppByDownload()">添加</el-button>
</template>
<template v-if="formData.importType == 'local'">
<el-button type="primary" @click="addAppByImport()">导入</el-button>
</template>
<template v-if="formData.importType == 'dev'">
<el-form-item label="本地路径">
<el-input v-model="formData.devPath" style="width: 280px;" @click="selectFile()" />
</el-form-item>
<el-button type="primary" @click="addAppByDev()">添加</el-button>
</template>
</el-form>
</template>
<style scoped>
.setting-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
margin-bottom: 20px;
gap: 20px;
}
.setting-item label {
display: block;
width: 100px;
flex-shrink: 0;
text-align: right;
}
.setting-item {
display: flex;
align-items: center;
.el-button {
margin:20px 100px
}
</style>

10
frontend/src/components/store/AppItem.vue

@ -9,7 +9,7 @@
/>
<div class="app-content">
<div class="app-title">{{ item?.name }}</div>
<div class="app-desc">{{ item?.desc ? item?.desc : item?.name }}</div>
<div class="app-desc">{{ item?.desc ?? item?.name }}</div>
<div class="app-button">
<div
v-if="installedList!.includes(item?.name)"
@ -17,7 +17,7 @@
<button @click="uninstall?.(item)">卸载</button>
</div>
<div v-else>
<button v-if="item?.url" @click="install?.(item)">安装</button>
<button v-if="item?.url || item?.path" @click="install?.(item)">安装</button>
</div>
</div>
</div>
@ -75,12 +75,12 @@ defineProps({
}
.app-item:hover {
box-shadow: 0 0 20px 2px #dddddd5b;
/* transform: translateY(-2px); */
transform: translateY(-2px);
}
/* .app-item:active {
.app-item:active {
box-shadow: 0 0 0px #ccc;
transform: translateY(0px);
} */
}
.app-button {
width: 100%;
display: flex;

477
frontend/src/components/store/Store.vue

@ -1,3 +1,70 @@
<script setup lang="ts">
import { inject, onMounted, ref } from "vue";
import { System } from "@/system";
import {notifySuccess, notifyError} from "@/util/msg";
import { t } from "@/i18n";
//import storeList from "@/assets/store.json";
import { getSystemKey, setSystemKey } from "@/system/config";
const sys: any = inject<System>("system");
const currentCateId = ref(0)
const currentTitle = ref(t("store.hots"))
const currentCate = ref('hots')
const categoryList = ['hots', 'work', 'development', 'games', 'education', 'news', 'shopping', 'social', 'utilities', 'others', 'add']
const categoryIcon = ['HomeFilled', 'Odometer', 'Postcard', 'TrendCharts', 'School', 'HelpFilled', 'ShoppingCart', 'ChatLineRound', 'MessageBox', 'Ticket', 'CirclePlusFilled']
const isready = ref(false);
const installed = getSystemKey("intstalledPlugins");
const apiUrl = getSystemKey("apiUrl");
const installedList: any = ref(installed);
const storeList: any = ref([])
onMounted(async() => {
await getList();
});
async function getList(){
if(currentCate.value == 'add')return;
const storeUrl = apiUrl + '/system/storeList?cate=' + currentCate.value
fetch(storeUrl).then(res => {
res.json().then(data => {
storeList.value = data
})
}).catch(() => {
notifyError(t("store.errorList"))
})
isready.value = true;
}
async function changeCate(index: number, item: string) {
currentCateId.value = index
currentCate.value = item
currentTitle.value = t("store." + item)
await getList()
}
function setCache() {
setSystemKey("intstalledPlugins", installedList.value);
setTimeout(() => {
sys.refershAppList();
}, 1000);
}
function install(item: any) {
//console.log(item)
if(item.url){
sys.fs.writeFile(
`${sys._options.userLocation}Desktop/${item.name}.url`,
`link::url::${item.url}::${item.icon}`
);
}
notifySuccess(t("install.success"))
installedList.value.push(item.name);
setCache();
}
function uninstall(item: any) {
sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`);
notifySuccess(t("uninstall.success"))
delete installedList.value[installedList.value.indexOf(item.name)];
setCache();
}
</script>
<template>
<div class="outer">
<div class="main">
@ -33,7 +100,7 @@
</div>
</div> -->
<div class="main-app">
<div v-for="item in storeList" v-if="currentTitle != t('store.add')" class="store-item" :key="item.name">
<div v-for="item in storeList" v-if="currentCate != 'add'" class="store-item" :key="item.name">
<AppItem :item="item" :installed-list="installedList" :install="install" :uninstall="uninstall" />
</div>
<AddApp v-else/>
@ -53,412 +120,6 @@
</div>
</div>
</template>
<script setup lang="ts">
import { inject, onMounted, ref } from "vue";
import { System, Dialog } from "@/system";
import { t } from "@/i18n";
//import storeList from "@/assets/store.json";
import { getSystemKey, setSystemKey } from "@/system/config";
const sys: any = inject<System>("system");
const currentCateId = ref(0)
const currentTitle = ref(t("store.hots"))
const categoryList = ['hots', 'work', 'development', 'games', 'education', 'news', 'shopping', 'social', 'utilities', 'others', 'add']
const categoryIcon = ['HomeFilled', 'Odometer', 'Postcard', 'TrendCharts', 'School', 'HelpFilled', 'ShoppingCart', 'ChatLineRound', 'MessageBox', 'Ticket', 'CirclePlusFilled']
const isready = ref(false);
const installed = getSystemKey("intstalledPlugins");
const installedList: any = ref(installed);
const storeList: any = ref([])
onMounted(() => {
setTimeout(() => {
if (!isready.value) {
isready.value = true;
}
}, 1000);
});
function changeCate(index: number, item: string) {
currentCateId.value = index
currentTitle.value = t("store." + item)
}
function setCache() {
setSystemKey("intstalledPlugins", installedList.value);
//localStorage.setItem("godoOS_installedApp", JSON.stringify(installedList.value))
setTimeout(() => {
sys.refershAppList();
}, 1000);
}
function install(item: any) {
//console.log(item)
sys.fs.writeFile(
`${sys._options.userLocation}Desktop/${item.name}.url`,
`link::url::${item.url}::${item.icon}`
);
Dialog.showMessageBox({
message: t("install.success"),
type: "info",
buttons: [t("confirm")],
});
installedList.value.push(item.name);
setCache();
}
function uninstall(item: any) {
sys.fs.unlink(`${sys._options.userLocation}Desktop/${item.name}.url`);
Dialog.showMessageBox({
message: t("uninstall.success"),
type: "info",
buttons: [t("confirm")],
});
delete installedList.value[installedList.value.indexOf(item.name)];
setCache();
}
</script>
<style>
/*
高宽分别对应横竖滚动条的尺寸*/
::-webkit-scrollbar {
width: 5px;
height: 16px;
background-color: #ffffff;
}
/*
内阴影+圆角*/
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 10px;
background-color: #ffffff;
}
/*
内阴影+圆角*/
::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: rgba(132, 132, 132, 0.537);
}
</style>
<style scoped>
.outer {
display: flex;
flex-direction: column;
height: 100%;
}
.uper {
padding: 0px 20px;
font-size: 12px;
height: 40px;
line-height: 40px;
flex-shrink: 0;
color: rgb(134, 134, 134);
background-color: rgb(243, 243, 243);
}
.store-handle {
width: 100%;
height: 40px;
user-select: none;
display: flex;
flex-direction: row;
justify-content: flex-end;
position: absolute;
top: 10px;
right: 20px;
}
.main {
width: 100%;
display: flex;
flex: 1;
overflow: hidden;
background-color: rgb(243, 243, 243);
}
.left {
width: 60px;
height: auto;
flex-shrink: 0;
background-color: rgb(243, 243, 243);
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
}
.left-icon {
width: 56px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
position: relative;
cursor: pointer;
&:hover {
background-color: rgb(243, 243, 243);
}
}
.icon-derc {
height: 20px;
width: 4px;
border-radius: 4px;
background-color: #363533;
position: absolute;
left: 2px;
}
.left-icon .ep_icon {
width: 20px;
height: 20px;
color: #363533;
stroke: #363533;
}
.store {
/* width: 100%; */
flex: 1;
background-color: rgb(255, 255, 255);
border-top-left-radius: 10px;
overflow: hidden;
}
.store-top {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
overflow: hidden;
}
.left-bar {
width: 60px;
flex-shrink: 0;
}
.right-main {
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
}
.main-title {
font-size: 26px;
font-weight: bold;
font-variant: small-caps;
font-variant-ligatures: discretionary-ligatures;
margin: 8px;
margin-top: 8px;
user-select: none;
overflow: hidden;
white-space: nowrap;
}
.sub-title {
padding-top: 30px;
font-size: 20px;
font-weight: bold;
margin: 10px;
user-select: none;
overflow: hidden;
white-space: nowrap;
}
.sub-tip {
font-size: 12px;
color: rgb(11, 31, 111);
margin-left: 10px;
user-select: none;
}
.swiper {
width: max-content;
height: 300px;
/* overflow: hidden; */
margin: 10px;
margin-top: 0px;
position: relative;
}
@keyframes swiperAni {
0%,
33% {
transform: translateX(0px);
}
36%,
66% {
transform: translateX(-600px);
}
69%,
96% {
transform: translateX(-1200px);
}
100% {
transform: translateX(0px);
}
}
.swiper-inner {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
animation: swiperAni 20s ease-in-out infinite;
}
.swiper-tab {
width: 600px;
height: 300px;
background-color: rgb(243, 243, 243);
border-radius: 20px;
box-shadow: 0px 10px 20px 1px #2524241f;
}
.swiper-tab img {
width: 100%;
height: 100%;
border-radius: 20px;
object-fit: cover;
}
.swiper-txt {
position: absolute;
top: 20px;
left: 30px;
font-weight: 600;
color: white;
z-index: 10;
text-shadow: 0px 0px 5px #00000058;
}
.main-app {
width: 100%;
/* height: 100%; */
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
align-items: flex-start;
padding: 10px;
overflow: auto;
align-content: flex-start;
}
.store-noready {
width: 100%;
height: 100%;
background-color: #cdcdcd69;
}
#wait {
position: absolute;
left: 50%;
top: calc(50% + 150px);
}
.waitd {
position: absolute;
width: 10px;
height: 10px;
left: 30px;
background-color: azure;
border-radius: 50%;
transform-origin: -15px 0;
}
#wait1 {
animation: dotAni1 2s linear infinite;
}
#wait2 {
animation: dotAni2 2s linear infinite;
}
#wait3 {
animation: dotAni3 2s linear infinite;
}
#wait4 {
animation: dotAni4 2s linear infinite;
}
@keyframes dotAni1 {
0% {
transform: rotateZ(0deg);
}
20% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni2 {
0% {
transform: rotateZ(0deg);
}
35% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni3 {
0% {
transform: rotateZ(0deg);
}
50% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes dotAni4 {
0% {
transform: rotateZ(0deg);
}
65% {
transform: rotateZ(240deg);
}
85% {
transform: rotateZ(290deg);
}
100% {
transform: rotateZ(360deg);
}
}
@import "@/assets/store.scss";
</style>

3
frontend/src/i18n/lang/en.json

@ -142,6 +142,7 @@
"social": "Social Networking",
"utilities": "Utilities",
"others": "Others",
"add":"Add"
"add":"Add",
"errorList":"Cannot get the list of applications"
}
}

3
frontend/src/i18n/lang/zh.json

@ -142,6 +142,7 @@
"social": "社交",
"utilities": "实用工具",
"others": "其他",
"add":"添加应用"
"add":"添加应用",
"errorList":"获取列表失败!"
}
}

9
godo/cmd/main.go

@ -26,6 +26,12 @@ func OsStart() {
router.Use(loggingMiddleware{}.Middleware)
staticDir := libs.GetStaticDir()
router.PathPrefix("/static").Handler(http.StripPrefix("/static", http.FileServer(http.Dir(staticDir))))
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet)
if libs.PathExists("./dist") {
router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./dist"))))
} else {
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
}
progressRouter := router.PathPrefix("/progress").Subrouter()
progressRouter.HandleFunc("/start/{name}", progress.StartProcess).Methods(http.MethodGet)
progressRouter.HandleFunc("/stop/{name}", progress.StopProcess).Methods(http.MethodGet)
@ -37,8 +43,7 @@ func OsStart() {
progressRouter.HandleFunc("/killport", progress.KillPortHandler).Methods(http.MethodGet)
progressRouter.HandleFunc("/app/{name}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
progressRouter.HandleFunc("/app/{name}/{subpath:.*}", progress.ForwardRequest).Methods(http.MethodGet, http.MethodPost)
router.HandleFunc("/ping", progress.Ping).Methods(http.MethodGet)
router.HandleFunc("/", progress.Ping).Methods(http.MethodGet)
router.HandleFunc("/system/updateInfo", sys.GetUpdateUrlHandler).Methods(http.MethodGet)
router.HandleFunc("/system/update", sys.UpdateAppHandler).Methods(http.MethodGet)
router.HandleFunc("/system/storeList", sys.GetStoreInfoHandler).Methods(http.MethodGet)

1
main.go

@ -16,7 +16,6 @@ var assets embed.FS
func main() {
// Create an instance of the app structure
app := App.NewApp()
// Create application with options
err := wails.Run(&options.App{
Title: "GodoOS",

Loading…
Cancel
Save