diff --git a/README.md b/README.md index 70b5f90..16101c9 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,9 @@ $ pnpm ... # install/preinstall scripts trigger during project setup │ │ ├── i18n # manage locale/translation strings for backend │ │ ├── log # define loggers for backend │ │ └── utils # helper functions +│ ├── service # service module, business layer +│ │ ├── service.go # initialize provided services +│ │ └── settings.go # functions about configure application │ ├── tray # tray module, presentations & services layer │ │ ├── icons # tray icons │ │ ├── menus # tray menus @@ -91,18 +94,21 @@ $ pnpm ... # install/preinstall scripts trigger during project setup │ │ ├── docs # auto-generated, try script `docs:build` │ │ ├── swagger # auto-generated, try script `swag:docs` │ │ ├── static.go # manage static sources -│ │ └── ... # other static sources +│ │ └── ... │ ├── air.go # special function for air to run web service individually │ ├── router.go # entry point of swaggo docs generator, manage routes for API │ └── web.go # web service ├── build # sources to use during wails build process │ ├── bin # auto-generated, try script `wails:dev` or `wails:build` -│ └── ... # wails related sources +│ └── ... ├── diagrams # diagrams about 4+1 view model ├── docs # vitepress documentation ├── frontend # sources related to frontend code, workplace managed by PNPM │ ├── packages # frontend components, icons, etc. -│ └── ... # wails frontend related sources +│ ├── src # wails frontend sources +│ │ ├── vite-env.d.ts # put go struct associated types into namespace app +│ │ └── ... +│ └── ... ├── main.go # wails main application, presentations & services layer ├── wails_life_cycle.go # wails life cycle ├── wails.json # wails CLI config diff --git a/backend/model/my_option.go b/backend/model/my_option.go index 93803ef..892c12b 100644 --- a/backend/model/my_option.go +++ b/backend/model/my_option.go @@ -8,6 +8,10 @@ type MyOption struct { Value string `` // Option value associated with name } +func (mo *MyOption) Load() *gorm.DB { + return db.Where(mo).Find(mo) +} + func (mo *MyOption) Update(newValue string) *gorm.DB { return db.Model(mo).Where(mo).Updates(MyOption{ Value: newValue, diff --git a/backend/service/service.go b/backend/service/service.go new file mode 100644 index 0000000..878bce3 --- /dev/null +++ b/backend/service/service.go @@ -0,0 +1,19 @@ +package service + +var ( + instance *service +) + +type service struct { + Settings *settings +} + +func init() { + instance = &service{ + Settings: &settings{}, + } +} + +func Settings() *settings { + return instance.Settings +} diff --git a/backend/service/settings.go b/backend/service/settings.go new file mode 100644 index 0000000..665e5ae --- /dev/null +++ b/backend/service/settings.go @@ -0,0 +1,54 @@ +package service + +import ( + "my-app/backend/app" + "my-app/backend/model" +) + +type settings struct{} + +func (s *settings) SaveOption(name string, value string) error { + option := model.MyOption{ + Name: name, + } + result := option.Update(value) + if result.Error == nil { + switch name { + case app.CfgDisplayLanguage: + app.App().Config().DisplayLanguage = value + case app.CfgColorTheme: + app.App().Config().ColorTheme = value + case app.CfgLogPath: + app.App().Config().LogPath = value + case app.CfgWebPortHttp: + app.App().Config().Web.PortHttp = value + case app.CfgWebPortHttps: + app.App().Config().Web.PortHttps = value + case app.CfgWebDirCerts: + app.App().Config().Web.DirCerts = value + } + } + return result.Error +} + +func (s *settings) GetOption(name string) string { + switch name { + case app.CfgDisplayLanguage: + return app.App().Config().DisplayLanguage + case app.CfgColorTheme: + return app.App().Config().ColorTheme + case app.CfgLogPath: + return app.App().Config().LogPath + case app.CfgWebPortHttp: + return app.App().Config().Web.PortHttp + case app.CfgWebPortHttps: + return app.App().Config().Web.PortHttps + case app.CfgWebDirCerts: + return app.App().Config().Web.DirCerts + } + return "" +} + +func (s *settings) GetOptions() *app.Config { + return app.App().Config() +} diff --git a/backend/services/settings.go b/backend/services/settings.go deleted file mode 100644 index 991faca..0000000 --- a/backend/services/settings.go +++ /dev/null @@ -1,22 +0,0 @@ -package services - -import ( - "my-app/backend/app" - "my-app/backend/model" -) - -func SaveColorThemeOption(theme string) error { - option := model.MyOption{ - Name: app.CfgColorTheme, - } - result := option.Update(theme) - return result.Error -} - -func SaveDisplayLanguageOption(lang string) error { - option := model.MyOption{ - Name: app.CfgDisplayLanguage, - } - result := option.Update(lang) - return result.Error -} diff --git a/backend/tray/listeners.go b/backend/tray/listeners.go index cdf0046..4a59674 100644 --- a/backend/tray/listeners.go +++ b/backend/tray/listeners.go @@ -4,7 +4,7 @@ import ( "fmt" "my-app/backend/app" "my-app/backend/pkg/i18n" - "my-app/backend/services" + "my-app/backend/service" "my-app/backend/tray/menus" "my-app/backend/web" @@ -63,12 +63,11 @@ func (t *tray) displayLanguageListener() menus.DisplayLanguageListener { t.colorTheme.SetLocale() t.quit.SetLocale() - if err := services.SaveDisplayLanguageOption(locale.Lang.Code); err != nil { + if err := service.Settings().SaveOption(app.CfgDisplayLanguage, locale.Lang.Code); err != nil { app.App().TrayLog().Fatalf("failed to update language option: %+v\n", err) } return true, func() { - app.App().Config().DisplayLanguage = lang t.refreshTooltip() } }, @@ -88,14 +87,12 @@ func (t *tray) colorThemeListener() menus.ColorThemeListener { } runtime.EventsEmit(t.wailsCtx, "onColorThemeChanged", theme) - if err := services.SaveColorThemeOption(theme); err != nil { + if err := service.Settings().SaveOption(app.CfgColorTheme, theme); err != nil { app.App().TrayLog().Fatalf("failed to update theme option: %+v\n", err) } return true, func() { - app.App().Config().ColorTheme = theme t.refreshTooltip() - runtime.Show(t.wailsCtx) } }, } diff --git a/diagrams/package.drawio b/diagrams/package.drawio index 6580226..a175204 100644 --- a/diagrams/package.drawio +++ b/diagrams/package.drawio @@ -161,6 +161,16 @@ + + + + + + + + + + diff --git a/diagrams/package.png b/diagrams/package.png index 8b3e452..14edd34 100644 Binary files a/diagrams/package.png and b/diagrams/package.png differ diff --git a/frontend/packages/components/form/index.ts b/frontend/packages/components/form/index.ts new file mode 100644 index 0000000..49e6c34 --- /dev/null +++ b/frontend/packages/components/form/index.ts @@ -0,0 +1,15 @@ +import type { App } from "vue"; +import Form from "./src/form.vue"; +import FormItem from "./src/form-item.vue"; +import FormGroup from "./src/form-group.vue"; + +Form.install = (app: App) => { + app.component(Form.name, Form); + app.component(FormItem.name, FormItem); + app.component(FormGroup.name, FormGroup); +}; + +export default Form; +export const MyForm = Form; +export const MyFormItem = FormItem; +export const MyFormGroup = FormGroup; diff --git a/frontend/packages/components/form/src/form-group.vue b/frontend/packages/components/form/src/form-group.vue new file mode 100644 index 0000000..56f4c1a --- /dev/null +++ b/frontend/packages/components/form/src/form-group.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/packages/components/form/src/form-item.vue b/frontend/packages/components/form/src/form-item.vue new file mode 100644 index 0000000..2f6cc62 --- /dev/null +++ b/frontend/packages/components/form/src/form-item.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/frontend/packages/components/form/src/form.vue b/frontend/packages/components/form/src/form.vue new file mode 100644 index 0000000..362e639 --- /dev/null +++ b/frontend/packages/components/form/src/form.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/packages/components/index.ts b/frontend/packages/components/index.ts index 93586bc..f955609 100644 --- a/frontend/packages/components/index.ts +++ b/frontend/packages/components/index.ts @@ -2,3 +2,4 @@ export * from "./icon"; export * from "./container"; export * from "./link"; export * from "./menu"; +export * from "./form"; diff --git a/frontend/packages/components/link/src/link.vue b/frontend/packages/components/link/src/link.vue index 4ceccd6..da43ecd 100644 --- a/frontend/packages/components/link/src/link.vue +++ b/frontend/packages/components/link/src/link.vue @@ -28,7 +28,6 @@ const props = withDefaults( type: "default", underline: true, disabled: false, - href: "", } ); const emit = defineEmits<{ @@ -36,7 +35,9 @@ const emit = defineEmits<{ }>(); const href = computed(() => { - return props.disabled || !props.href ? undefined : props.href; + if (!props.disabled) { + return props.href || undefined; + } }); const handleClick = (e: MouseEvent) => { diff --git a/frontend/packages/components/menu/src/menu-group.vue b/frontend/packages/components/menu/src/menu-group.vue index 344a320..ec7b45e 100644 --- a/frontend/packages/components/menu/src/menu-group.vue +++ b/frontend/packages/components/menu/src/menu-group.vue @@ -1,6 +1,6 @@ diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 43e7175..d4e3c8b 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1,5 +1,10 @@ { "lang": "English", + "theme": { + "system": "System Default", + "light": "Light", + "dark": "Dark" + }, "menu": { "main": { "title": "Main", @@ -15,5 +20,19 @@ "footer": { "openVitePress": "Open VitePress Docs", "openSwagger": "Open Swagger Docs" + }, + "settings": { + "general": { + "legend": "General", + "displayLanguage": "Display Language", + "colorTheme": "Color Theme", + "logPath": "Log Path" + }, + "webService": { + "legend": "Web Service", + "portHttp": "HTTP Port", + "portHttps": "HTTPS Port", + "dirCerts": "TLS Certificate Path" + } } } diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index fb8a0c5..7ec2a23 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -1,5 +1,10 @@ { "lang": "简体中文", + "theme": { + "system": "跟随系统", + "light": "浅色", + "dark": "深色" + }, "menu": { "main": { "title": "主菜单", @@ -15,5 +20,19 @@ "footer": { "openVitePress": "打开 VitePress 文档", "openSwagger": "打开 Swagger 文档" + }, + "settings": { + "general": { + "legend": "通用", + "displayLanguage": "显示语言", + "colorTheme": "颜色主题", + "logPath": "日志保存路径" + }, + "webService": { + "legend": "Web 服务", + "portHttp": "HTTP 端口", + "portHttps": "HTTPS 端口", + "dirCerts": "TLS 证书路径" + } } } diff --git a/frontend/src/store/color-theme.ts b/frontend/src/store/color-theme.ts index 62284bc..3f38881 100644 --- a/frontend/src/store/color-theme.ts +++ b/frontend/src/store/color-theme.ts @@ -1,7 +1,19 @@ import { defineStore } from "pinia"; +const prefix = "my-theme-"; + export const useColorTheme = defineStore("color-theme", { state: () => ({ theme: "system", }), + actions: { + changeTheme(theme: string) { + const root = document.querySelector(":root"); + if (root) { + root.classList.remove(prefix + this.theme); + root.classList.add(prefix + theme); + } + this.theme = theme; + }, + }, }); diff --git a/frontend/src/style.scss b/frontend/src/style.scss index a50da35..06df14d 100644 --- a/frontend/src/style.scss +++ b/frontend/src/style.scss @@ -12,10 +12,10 @@ -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; user-select: none; -} -body { - margin: 0; + body { + margin: 0; + } } @media (prefers-color-scheme: light) { diff --git a/frontend/src/views/About.vue b/frontend/src/views/About.vue index c2f70d6..b7a5dae 100644 --- a/frontend/src/views/About.vue +++ b/frontend/src/views/About.vue @@ -4,7 +4,7 @@ import { useColorTheme } from "../store/color-theme"; import { EventsOn } from "../../wailsjs/runtime"; const { t, locale } = useI18n(); -const colorTheme = useColorTheme(); +const { theme, changeTheme } = useColorTheme();