Compare commits

1 Commits

Author SHA1 Message Date
f27b647eb0 sync 2025-03-13 20:45:12 +07:00
22 changed files with 248 additions and 215 deletions

View File

@@ -50,13 +50,14 @@ wails3 generate bindings -ts
Для финальной сборки запустите эту команду в директории проекта: Для финальной сборки запустите эту команду в директории проекта:
``` ```
PRODUCTION=true wails3 build go env -w CGO_ENABLED=1
wails3 build -clean -upx -v 2 -webview2 embed
``` ```
**Перед релизом не забыть**: **Перед релизом не забыть**:
- убедиться, что дефолтные данные правильные
- убедиться, что приложение запускается - поместить все нужные asset'ы в папку assets
- (опционально) поместить все нужные asset'ы в папку assets - изменить версию схемы БД (пока не нужно)
- приложить сопроводительную записку. - приложить сопроводительную записку.
## Работа без GitHub ## Работа без GitHub

View File

@@ -13,7 +13,7 @@ tasks:
cmds: cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars: vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags "production sqlite_icu" -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l" -tags "sqlite_icu"{{end}}' BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}'
env: env:
GOOS: darwin GOOS: darwin
CGO_ENABLED: 1 CGO_ENABLED: 1

View File

@@ -13,7 +13,7 @@ tasks:
cmds: cmds:
- go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}} - go build {{.BUILD_FLAGS}} -o {{.BIN_DIR}}/{{.APP_NAME}}
vars: vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags "production sqlite_icu" -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l" -tags "sqlite_icu" {{end}}' BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s"{{else}}-gcflags=all="-l"{{end}}'
env: env:
GOOS: linux GOOS: linux
CGO_ENABLED: 1 CGO_ENABLED: 1

View File

@@ -18,7 +18,7 @@ tasks:
- cmd: rm -f *.syso - cmd: rm -f *.syso
platforms: [linux, darwin] platforms: [linux, darwin]
vars: vars:
BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags "production sqlite_icu" -trimpath -ldflags="-w -s -H windowsgui"{{else}}-gcflags=all="-l" -tags "sqlite_icu"{{end}}' BUILD_FLAGS: '{{if eq .PRODUCTION "true"}}-tags production -trimpath -ldflags="-w -s -H windowsgui"{{else}}-gcflags=all="-l"{{end}}'
env: env:
GOOS: windows GOOS: windows
CGO_ENABLED: 1 CGO_ENABLED: 1

View File

@@ -31,6 +31,7 @@ const load = async () => {
onMounted(async () => { onMounted(async () => {
await load(); await load();
console.log(await SortedByOrder({"Comments": "DESC"}))
}); });
const scheme: Scheme<Author> = reactive({ const scheme: Scheme<Author> = reactive({

View File

@@ -17,6 +17,7 @@ const pushOrRemove = (option: T) => {
} else { } else {
selected.value.push(option) selected.value.push(option)
} }
//setNullIds()
} }
const setNullIds = () => { const setNullIds = () => {
@@ -26,11 +27,12 @@ const setNullIds = () => {
}) })
} }
//onMounted(setNullIds)
</script> </script>
<template> <template>
<div class=""> <div class="">
<ul class="max-h-48 h-auto overflow-y-auto background rounded-md p-3 w-full native-border"> <ul class="max-h-48 h-auto overflow-y-auto background rounded-md p-3 w-full border-gray-500 border">
<li v-for="option in options" :key="option.Id" class="flex items-center gap-2"> <li v-for="option in options" :key="option.Id" class="flex items-center gap-2">
<Checkbox :checked="selected.some(item => item.Id == option.Id)" @click="pushOrRemove(option)" /> <Checkbox :checked="selected.some(item => item.Id == option.Id)" @click="pushOrRemove(option)" />
<label :for="option.Id.toString()">{{ structView(option, path) }}</label> <label :for="option.Id.toString()">{{ structView(option, path) }}</label>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Table from "../table/Table.vue"; import Table from "../table/Table.vue";
import {onMounted, reactive, watch} from "vue"; import { onMounted, reactive } from "vue";
import { getDefaultValues } from "../utils/structs/defaults.util"; import { getDefaultValues } from "../utils/structs/defaults.util";
import Service from "./post.service.ts"; import Service from "./post.service.ts";
import type { Scheme } from "../types/scheme.type"; import type { Scheme } from "../types/scheme.type";
@@ -16,7 +16,7 @@ const posttypeService = new PosttypeService();
import CommentService from "../comment/comment.service.ts"; import CommentService from "../comment/comment.service.ts";
import { SortedByOrder } from "../../bindings/app/internal/services/postservice.ts"; import { SortedByOrder } from "../../bindings/app/internal/services/postservice.ts";
import {getDefaultSortOptions} from "../utils/structs/default-sort-options.util.ts"; import type { Search } from "../types/search.type.ts";
const commentService = new CommentService(); const commentService = new CommentService();
@@ -33,12 +33,13 @@ const load = async () => {
(scheme as any).Comments.type!.nested!.values = (scheme as any).Comments.type!.nested!.values =
await commentService.readAll(); await commentService.readAll();
items.value = await service.sort(sortOptions.value) ; items.value = await service.readAll();
return items.value; return items.value;
}; };
onMounted(async () => { onMounted(async () => {
await load(); await load();
console.log(await SortedByOrder({ "Author": "DESC", "Text": "ASC" }));
}); });
const scheme: Scheme<Post> = reactive({ const scheme: Scheme<Post> = reactive({
@@ -116,6 +117,9 @@ const scheme: Scheme<Post> = reactive({
values: [], values: [],
field: ["Text"], field: ["Text"],
}, },
},
customWindow: {
create: true,
} }
}, },
}); });
@@ -130,15 +134,15 @@ const validate: Validate<Post> = (entity) => {
}; };
}; };
const search = async (input: string) => { const search: Search<Post> = (input) => {
} }
const sortOptions = ref(getDefaultSortOptions(scheme))
</script> </script>
<template> <template>
<Table :scheme :service :get-defaults :load :items :validate @on-search="search" v-model:sort-options="sortOptions"></Table> <Table :scheme :service :get-defaults :load :items :validate @on-search="search">
<template #CommentsCreate="{ data }">
<p>{{ data }}</p>
</template>
</Table>
</template> </template>

View File

@@ -4,11 +4,10 @@ import {
Delete, Delete,
GetById, GetById,
Update, Update,
Count, SortedByOrder, Count,
} from "../../bindings/app/internal/services/postservice.ts"; } from "../../bindings/app/internal/services/postservice.ts";
import type { Post } from "../../bindings/app/internal/services"; import type { Post } from "../../bindings/app/internal/services";
import type { IService } from "../types/service.type.ts"; import type { IService } from "../types/service.type.ts";
import type {SortOptions} from "../types/sort-options.type.ts";
export default class PostService implements IService<Post> { export default class PostService implements IService<Post> {
async read(id: number) { async read(id: number) {
@@ -34,8 +33,4 @@ export default class PostService implements IService<Post> {
async count() { async count() {
return await Count(); return await Count();
} }
async sort(options: SortOptions<Post>) {
return await SortedByOrder(options) as Post[]
}
} }

View File

@@ -5,25 +5,10 @@
html, body, .background { html, body, .background {
background: white; background: white;
color: rgb(51, 65, 85);
}
.secondary-background {
background: var(--p-content-background)
}
.native-border {
border: 1px solid var(--p-select-border-color);
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html, body, .background { html, body, .background {
background: #121212; background: #121212;
color: white;
} }
.native-border {
border: 1px solid rgb(100, 100, 109);
}
} }

View File

@@ -62,7 +62,7 @@ async function handleSave() {
</script> </script>
<template> <template>
<Dialog v-model:visible="showCreate" class="w-[500px]"> <Dialog v-model:visible="showCreate">
<template #header> <template #header>
<h1>{{ props.updateMode ? 'Изменить' : 'Создать' }} {{ props.name?.toLowerCase() }}</h1> <h1>{{ props.updateMode ? 'Изменить' : 'Создать' }} {{ props.name?.toLowerCase() }}</h1>
</template> </template>

View File

@@ -1,25 +0,0 @@
<script setup lang="ts" generic="T extends IEntity">
import type {IEntity} from "../types/entity.type.ts";
import type {SortOptions} from "../types/sort-options.type.ts";
import type {Scheme} from "../types/scheme.type.ts";
import {Select} from 'primevue'
import {watch} from "vue";
const options = defineModel<SortOptions<T>>()
const optionsKeys = Object.keys(options.value as T) as (keyof T)[]
defineProps<{
scheme: Scheme<T>
load: () => Promise<T[]>
}>()
</script>
<template>
<ul class="flex flex-col gap-2 native-border secondary-background p-3 rounded-md">
<li v-for="optionKey in optionsKeys" class="flex items-center justify-between w-full">
<h1>{{ scheme[optionKey].russian }}</h1>
<Select size="small" class="w-24" :options="['ASC', 'DESC']" v-model="options![optionKey]" @value-change="load"></Select>
</li>
</ul>
</template>

View File

@@ -8,11 +8,13 @@ import FloatingButton from "../components/buttons/FloatingButton.vue";
import type { IEntity } from "../types/entity.type"; import type { IEntity } from "../types/entity.type";
import DialogWindow from "./DialogWindow.vue"; import DialogWindow from "./DialogWindow.vue";
import { viewDate } from "../utils/date/view.util"; import { viewDate } from "../utils/date/view.util";
import SortingOptions from "./SortingOptions.vue";
import type {SortOptions} from "../types/sort-options.type.ts";
const props = defineProps<TableProps<T>>(); const props = defineProps<TableProps<T>>();
onMounted(async () => {
props.load();
});
type Key = keyof T; type Key = keyof T;
const keys = Object.keys(props.scheme) as Key[]; const keys = Object.keys(props.scheme) as Key[];
const emits = defineEmits<TableEmits>(); const emits = defineEmits<TableEmits>();
@@ -54,8 +56,8 @@ watch(createItem, (value) => {
}); });
const handleFloatingButtonClick = () => { const handleFloatingButtonClick = () => {
emits('onCreateOpen') emits("onCreateOpen");
emits('onOpen') emits("onOpen");
showCreate.value = true; showCreate.value = true;
}; };
const slots = defineSlots(); const slots = defineSlots();
@@ -63,60 +65,70 @@ const slots = defineSlots();
const createSlotName = (key: any) => key + "Create"; const createSlotName = (key: any) => key + "Create";
const updateSlotName = (key: any) => key + "Update"; const updateSlotName = (key: any) => key + "Update";
watch(() => props.items, () => { watch(
() => props.items,
() => {
if (props.colorize) { if (props.colorize) {
const trs = document.querySelectorAll("tr"); const trs = document.querySelectorAll("tr");
props.items.forEach(item => { props.items.forEach((item) => {
const tr = trs[item.Id]; const tr = trs[item.Id];
if (tr) { if (tr) {
tr.style.backgroundColor = props.colorize!(item); tr.style.backgroundColor = props.colorize!(item);
} }
}) });
} }
}) },
);
const input = ref('')
const sortOptions = defineModel<SortOptions<T>>('sort-options');
const showSortOptions = ref(false)
const input = defineModel<string>("search", { default: "" });
</script> </script>
<template> <template>
<div class="m-2 flex flex-col items-center gap-2"> <div class="flex h-10 justify-center m-4 gap-2">
<div class="flex flex-col gap-2 relative"> <InputText v-model="input" placeholder="Поиск" class="h-full w-64" />
<div class="flex items-center justify-center gap-2 h-10"> <Button class="h-full aspect-square" severity="secondary">
<Button severity="secondary" class="h-full aspect-square" @click="showSortOptions = !showSortOptions">
<span class="pi pi-sort"></span>
</Button>
<InputText class="h-full w-64" v-model="input"/>
<Button severity="secondary" class="h-full aspect-square" @click="emits('onSearch', input)">
<span class="pi pi-search"></span> <span class="pi pi-search"></span>
</Button> </Button>
</div> </div>
<SortingOptions v-model="sortOptions as SortOptions<T>" :scheme v-if="showSortOptions" <DialogWindow
class="absolute z-10 w-full top-full translate-y-2" :load="props.load"></SortingOptions> :name
</div> :load
:items
</div> :validate
<DialogWindow :name :load :items :validate :scheme :service :get-defaults v-model:item="<T>createItem" :scheme
v-model:show="showCreate" @on-save="data => emits('onSave', data)" :service
@on-save-create="data => emits('onSaveCreate', data)"> :get-defaults
v-model:item="<T>createItem"
v-model:show="showCreate"
@on-save="(data) => emits('onSave', data)"
@on-save-create="(data) => emits('onSaveCreate', data)"
>
<template v-for="key in keys" #[key]="{ data }"> <template v-for="key in keys" #[key]="{ data }">
<slot :name="<string>key" :data="data"></slot> <slot :name="<string>key" :data></slot>
</template> </template>
<template v-for="key in keys" #[createSlotName(key)]="{ data }"> <template v-for="key in keys" #[createSlotName(key)]="{ data }">
<slot :name="createSlotName(key)" :data="data"></slot> <slot :name="createSlotName(key)" :data></slot>
</template> </template>
</DialogWindow> </DialogWindow>
<DialogWindow :name :load :items :validate :scheme update-mode :service :get-defaults v-model:item="<T>updateItem" <DialogWindow
v-model:show="showUpdate" @on-save="data => emits('onSave', data)" :name
@on-save-update="data => emits('onSaveUpdate', data)"> :load
:items
:validate
:scheme
update-mode
:service
:get-defaults
v-model:item="<T>updateItem"
v-model:show="showUpdate"
@on-save="(data) => emits('onSave', data)"
@on-save-update="(data) => emits('onSaveUpdate', data)"
>
<template v-for="key in keys" #[key]="{ data }"> <template v-for="key in keys" #[key]="{ data }">
<slot :name="<string>key" :data="data"></slot> <slot :name="<string>key" :data></slot>
</template> </template>
<template v-for="key in keys" #[updateSlotName(key)]="{ data }"> <template v-for="key in keys" #[updateSlotName(key)]="{ data }">
<slot :name="updateSlotName(key)" :data="data"></slot> <slot :name="updateSlotName(key)" :data></slot>
</template> </template>
</DialogWindow> </DialogWindow>
<div> <div>
@@ -125,17 +137,31 @@ const showSortOptions = ref(false)
<p>{{ props.name }}</p> <p>{{ props.name }}</p>
</template> </template>
<template v-for="key in keys"> <template v-for="key in keys">
<Column :header="props.scheme[key]?.russian" v-if="!props.scheme[key].hidden"> <Column
:header="props.scheme[key]?.russian"
v-if="!props.scheme[key].hidden"
>
<template #body="{ data }"> <template #body="{ data }">
<p class="truncate max-w-56" v-tooltip="viewDate(manyStructsView( <p
class="truncate max-w-56"
v-tooltip="
viewDate(
manyStructsView(
data[key], data[key],
props.scheme[key]?.type?.nested?.field, props.scheme[key]?.type?.nested?.field,
), scheme[key]?.type?.primitive)"> ),
scheme[key]?.type?.primitive,
)
"
>
{{ {{
viewDate(manyStructsView( viewDate(
manyStructsView(
data[key], data[key],
props.scheme[key]?.type?.nested?.field, props.scheme[key]?.type?.nested?.field,
), scheme[key]?.type?.primitive) ),
scheme[key]?.type?.primitive,
)
}} }}
</p> </p>
</template> </template>
@@ -144,27 +170,31 @@ const showSortOptions = ref(false)
<Column header="Действия"> <Column header="Действия">
<template #body="{ data }"> <template #body="{ data }">
<div class="flex gap-2"> <div class="flex gap-2">
<Button severity="secondary" icon="pi pi-pencil" @click="async () => { <Button
await emits('onUpdateOpen') severity="secondary"
await emits('onOpen') icon="pi pi-pencil"
updateItem = data @click="
}"></Button> async () => {
<Button severity="danger" icon="pi pi-trash" @click="async () => { await emits('onUpdateOpen');
await emits('onDelete', data) await emits('onOpen');
await props.service.delete(data.Id) updateItem = data;
await load() }
}"></Button> "
></Button>
<Button
severity="danger"
icon="pi pi-trash"
@click="
async () => {
await emits('onDelete', data);
await props.service.delete(data.Id);
await load();
}
"
></Button>
</div> </div>
</template> </template>
</Column> </Column>
<template #paginatorstart>
<Button severity="secondary" @click="props.load">
<span class="pi pi-refresh"></span>
</Button>
</template>
<template #paginatorend>
<span></span>
</template>
</DataTable> </DataTable>
<FloatingButton @click="handleFloatingButtonClick" /> <FloatingButton @click="handleFloatingButtonClick" />
</div> </div>

View File

@@ -0,0 +1,3 @@
import type { IEntity } from "./entity.type";
export type Search<T extends IEntity> = (input: string) => T[]

View File

@@ -1,6 +0,0 @@
import type { IEntity } from "./entity.type";
import type { Sorting } from "./sorting.type";
export type SortOptions<T extends IEntity> = {
[key in keyof Partial<T>]: Sorting
}

View File

@@ -0,0 +1,7 @@
import type { IEntity } from "./entity.type";
export type SortOptions<T extends IEntity> = {
[key in keyof T]: "ASC" | "DESC"
}
export type Sort<T extends IEntity> = (options: SortOptions<T>) => T[]

View File

@@ -1 +0,0 @@
export type Sorting = "DESC" | "ASC"

View File

@@ -1,6 +1,8 @@
import type { IEntity } from "./entity.type"; import type { IEntity } from "./entity.type";
import type { Scheme } from "./scheme.type"; import type { Scheme } from "./scheme.type";
import type { Search } from "./search.type";
import type { IService } from "./service.type"; import type { IService } from "./service.type";
import type { Sort } from "./sort.type";
import type { Validate } from "./validate.type"; import type { Validate } from "./validate.type";
export interface TableProps<T extends IEntity> { export interface TableProps<T extends IEntity> {

View File

@@ -1,16 +0,0 @@
import type { IEntity } from "../../types/entity.type";
import type { Scheme } from "../../types/scheme.type";
import type { SortOptions } from "../../types/sort-options.type";
export const getDefaultSortOptions = <T extends IEntity>(scheme: Scheme<T>): SortOptions<T> => {
const keys = Object.keys(scheme) as (keyof T)[]
const result: Partial<SortOptions<T>> = {}
keys.forEach(key => {
if (!scheme[key].hidden && key !== 'entityId' && !scheme[key].many) {
result[key] = 'ASC'
}
})
return result as SortOptions<T>
}

View File

@@ -0,0 +1,6 @@
import type { IEntity } from "../../types/entity.type";
import type { Scheme } from "../../types/scheme.type";
export const defaultSort = <T extends IEntity>(scheme: Scheme<T>) => {
}

18
package-lock.json generated Normal file
View File

@@ -0,0 +1,18 @@
{
"name": "boilerplate",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@wailsio/runtime": "3.0.0-alpha.66"
}
},
"node_modules/@wailsio/runtime": {
"version": "3.0.0-alpha.66",
"resolved": "https://registry.npmjs.org/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",
"integrity": "sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==",
"license": "MIT"
}
}
}

5
package.json Normal file
View File

@@ -0,0 +1,5 @@
{
"dependencies": {
"@wailsio/runtime": "3.0.0-alpha.66"
}
}

22
pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,22 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@wailsio/runtime':
specifier: 3.0.0-alpha.66
version: 3.0.0-alpha.66
packages:
'@wailsio/runtime@3.0.0-alpha.66':
resolution: {integrity: sha512-ENLu8rn1griL1gFHJqkq1i+BVxrrA0JPJHYneUJYuf/s54kjuQViW0RKDEe/WTDo56ABpfykrd/T8OYpPUyXUw==}
snapshots:
'@wailsio/runtime@3.0.0-alpha.66': {}