初步代替几个bat脚本的功能

This commit is contained in:
zhbaor 2024-09-23 16:12:34 +08:00
commit d0fbc89012
19 changed files with 4002 additions and 0 deletions

162
.gitignore vendored Normal file
View file

@ -0,0 +1,162 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

1
launcher.json Normal file
View file

@ -0,0 +1 @@
{"page": "init", "branch": "fast"}

57
main.py Normal file
View file

@ -0,0 +1,57 @@
import json
import os
from pathlib import Path
from subprocess import PIPE, STDOUT, Popen
import webview
config_path = Path("launcher.json")
try:
with config_path.open("r") as f:
config = json.load(f)
except Exception:
config = {
"page": "init",
"branch": "slow",
}
def custom_event(name, data):
data = json.dumps({name: data})
js = f"var event = new CustomEvent('log', {{detail: {data}}}); window.dispatchEvent(event);"
window.evaluate_js(js)
class Api:
def get_branch(self):
return config["branch"]
def set_branch(self, branch):
config["branch"] = branch
def get_page(self):
return config["page"]
def set_page(self, page):
config["page"] = page
def run(self, command, cwd=None):
with Popen(
os.path.normpath(command),
stdout=PIPE,
stderr=STDOUT,
shell=True,
bufsize=1,
text=True,
cwd=cwd,
) as p:
for line in p.stdout:
custom_event("log", line)
window = webview.create_window("mower-ng launcher", "dist/index.html", js_api=Api())
webview.start()
with config_path.open("w") as f:
json.dump(config, f)

14
ui/.eslintrc.cjs Normal file
View file

@ -0,0 +1,14 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
'extends': [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-prettier/skip-formatting'
],
parserOptions: {
ecmaVersion: 'latest'
}
}

30
ui/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

9
ui/.prettierrc.json Normal file
View file

@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none",
"endOfLine": "auto"
}

7
ui/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}

13
ui/index.html Normal file
View file

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body style="background-color: black">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
ui/jsconfig.json Normal file
View file

@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

3383
ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

30
ui/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "ui",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.4.29"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.8.0",
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue": "^5.0.5",
"@vue/eslint-config-prettier": "^9.0.0",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.23.0",
"naive-ui": "^2.39.0",
"prettier": "^3.2.5",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vfonts": "^0.0.3",
"vite": "^5.3.1"
}
}

79
ui/src/App.vue Normal file
View file

@ -0,0 +1,79 @@
<script setup>
import Init from '@/pages/Init.vue'
import Launch from '@/pages/Launch.vue'
import Update from '@/pages/Update.vue'
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
const loading = ref(true)
const page = ref(null)
function load_config() {
console.log('load_config')
console.log(pywebview)
pywebview.api.get_page().then((value) => {
page.value = value
loading.value = false
})
}
const log = ref('')
provide('log', log)
const log_ele = ref(null)
provide('log_ele', log_ele)
watch(log, () => {
nextTick(() => {
log_ele.value?.scrollTo({ position: 'bottom' })
})
})
onMounted(() => {
if (window.pywebview && pywebview.api) {
load_config()
} else {
window.addEventListener('pywebviewready', load_config)
}
window.addEventListener('log', (e) => {
log.value += e.detail.log
})
})
function set_page(value) {
log.value = ''
pywebview.api.set_page(value)
}
const running = ref(false)
provide('running', running)
const steps = ref([])
provide('steps', steps)
</script>
<template>
<n-config-provider :theme="darkTheme" :locale="zhCN" :dateLocale="dateZhCN">
<n-spin v-if="loading" class="container">
<template #description>加载中</template>
</n-spin>
<n-tabs
v-else
type="card"
placement="left"
class="container"
:default-value="page"
@update:value="set_page"
:disabled="running"
>
<n-tab-pane name="init" tab="初始化"><init /></n-tab-pane>
<n-tab-pane name="update" tab="更新代码"><update /></n-tab-pane>
<n-tab-pane name="launch" tab="启动程序"><launch /></n-tab-pane>
</n-tabs>
<n-global-style />
</n-config-provider>
</template>
<style scoped>
.container {
width: 100vw;
height: 100vh;
}
</style>

View file

@ -0,0 +1,48 @@
<script setup>
import PlayIcon from '@vicons/ionicons5/Play'
import { inject } from 'vue'
const running = inject('running')
const log = inject('log')
const steps = inject('steps')
const current_step = inject('current_step')
const current_state = inject('current_state')
async function start() {
log.value = ''
running.value = true
for (const [i, step] of steps.value.entries()) {
current_step.value = i + 1
current_state.value = 'process'
for (const cmd of step.command) {
await pywebview.api.run(cmd, step.cwd)
}
}
current_state.value = 'finish'
running.value = false
}
</script>
<template>
<n-button class="float" type="primary" :loading="running" :disabled="running" @click="start">
<template #icon>
<n-icon>
<play-icon />
</n-icon>
</template>
{{ running ? '' : '运行' }}
</n-button>
</template>
<style scoped>
.float {
position: absolute;
right: 25px;
bottom: 25px;
opacity: 0.8;
}
.float:hover {
opacity: 1;
}
</style>

View file

@ -0,0 +1,8 @@
<script setup>
const log = inject('log')
const log_ele = inject('log_ele')
</script>
<template>
<n-log :log="log" style="flex-grow: 1" ref="log_ele" />
</template>

7
ui/src/main.js Normal file
View file

@ -0,0 +1,7 @@
import 'vfonts/Lato.css'
import 'vfonts/FiraCode.css'
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')

26
ui/src/pages/Init.vue Normal file
View file

@ -0,0 +1,26 @@
<script setup>
const steps = ref([
{ title: '设置Git LFS', command: ['git/bin/git lfs install'] },
{ title: '安装pip', command: ['../python/python -m ensurepip --default-pip'] },
{
title: '下载代码',
command: ['git/bin/git clone https://git.zhaozuohong.vip/mower-ng/mower-ng.git --branch slow']
}
])
provide('steps', steps)
const current_step = ref(1)
provide('current_step', current_step)
const current_state = ref('wait')
provide('current_state', current_state)
</script>
<template>
<n-flex vertical style="gap: 16px; height: 100%; padding: 16px; box-sizing: border-box">
<n-alert title="以下步骤仅需运行一次" type="warning" />
<n-steps :current="current_step" :status="current_state" size="small">
<n-step v-for="step in steps" :title="step.title" />
</n-steps>
<log-component />
</n-flex>
<float-button />
</template>

33
ui/src/pages/Launch.vue Normal file
View file

@ -0,0 +1,33 @@
<script setup>
function webview() {
pywebview.api.run('start ../python/pythonw webview_ui.py', './mower-ng')
}
function manager() {
pywebview.api.run('start ../python/pythonw manager.py', './mower-ng')
}
</script>
<template>
<n-flex
vertical
style="
gap: 16px;
height: 100%;
padding: 16px;
box-sizing: border-box;
justify-content: center;
align-items: center;
"
>
<n-button class="launch-btn" @click="webview">单开运行</n-button>
<n-button class="launch-btn" @click="manager">多开器</n-button>
</n-flex>
</template>
<style scoped>
.launch-btn {
width: 160px;
height: 48px;
}
</style>

56
ui/src/pages/Update.vue Normal file
View file

@ -0,0 +1,56 @@
<script setup>
const branch = ref(null)
onMounted(() => {
pywebview.api.get_branch().then((value) => {
branch.value = value
})
})
watch(branch, () => {
pywebview.api.set_branch(branch.value)
})
const steps = ref([
{
title: '下载源码',
command: [
'../git/bin/git fetch',
`../git/bin/git switch -f ${branch.value}`,
`../git/bin/git reset --hard origin/${branch.value}`
],
cwd: './mower-ng'
},
{
title: '安装依赖',
command: [
'../python/Scripts/pip install -i https://mirror.sjtu.edu.cn/pypi/web/simple -r requirements.txt'
],
cwd: './mower-ng'
}
])
provide('steps', steps)
const current_step = ref(1)
provide('current_step', current_step)
const current_state = ref('wait')
provide('current_state', current_state)
</script>
<template>
<n-flex vertical style="gap: 16px; height: 100%; padding: 16px; box-sizing: border-box">
<n-flex>
<div>代码分支</div>
<n-radio-group v-model:value="branch">
<n-flex>
<n-radio value="fast">fast</n-radio>
<n-radio value="slow">slow</n-radio>
</n-flex>
</n-radio-group>
</n-flex>
<n-steps :current="current_step" :status="current_state" size="small">
<n-step v-for="step in steps" :title="step.title" />
</n-steps>
<log-component />
</n-flex>
<float-button />
</template>

31
ui/vite.config.js Normal file
View file

@ -0,0 +1,31 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// vueDevTools(),
AutoImport({
imports: [
'vue',
{
'naive-ui': ['useDialog', 'useMessage', 'useNotification', 'useLoadingBar']
}
]
}),
Components({
resolvers: [NaiveUiResolver()]
})
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})