feat(wip): 장수패널에 ag-grid

This commit is contained in:
2022-04-07 03:03:38 +09:00
parent 1a04e2c53b
commit f3df6db607
5 changed files with 233 additions and 72 deletions
+17 -5
View File
@@ -1,7 +1,7 @@
<template> <template>
<BContainer id="container" :toast="{ root: true }" class="pageNationGeneral bg0"> <BContainer id="container" :toast="{ root: true }" class="pageNationGeneral bg0">
<TopBackBar :title="title" /> <TopBackBar :title="title" />
<GeneralList :list="generalList" /> <GeneralList :list="generalList" :height="'fill'" />
<BottomBar /> <BottomBar />
</BContainer> </BContainer>
</template> </template>
@@ -23,7 +23,7 @@ import { merge2DArrToObjectArr } from "./util/merge2DArrToObjectArr";
import type { GeneralListItem } from "./defs/API/Nation"; import type { GeneralListItem } from "./defs/API/Nation";
import GeneralList from "./components/GeneralList.vue"; import GeneralList from "./components/GeneralList.vue";
const generalList = ref(new Map<number, GeneralListItem>()); const generalList = ref<GeneralListItem[]>([]);
const title = "세력 장수"; const title = "세력 장수";
@@ -33,13 +33,13 @@ onMounted(async () => {
//XXX: 로직상 똑같은데.... //XXX: 로직상 똑같은데....
if (permission == 0) { if (permission == 0) {
const rawGeneralList = merge2DArrToObjectArr(column, list); const rawGeneralList = merge2DArrToObjectArr(column, list);
generalList.value = new Map(rawGeneralList.map((v) => [v.no, { permission, ...v }])); generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
} else if (permission == 1) { } else if (permission == 1) {
const rawGeneralList = merge2DArrToObjectArr(column, list); const rawGeneralList = merge2DArrToObjectArr(column, list);
generalList.value = new Map(rawGeneralList.map((v) => [v.no, { permission, ...v }])); generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
} else if ([2, 3, 4].includes(permission)) { } else if ([2, 3, 4].includes(permission)) {
const rawGeneralList = merge2DArrToObjectArr(column, list); const rawGeneralList = merge2DArrToObjectArr(column, list);
generalList.value = new Map(rawGeneralList.map((v) => [v.no, { permission, ...v }])); generalList.value = rawGeneralList.map((v) => {return { permission, ...v }});
} else { } else {
throw `?? ${permission}`; throw `?? ${permission}`;
} }
@@ -50,3 +50,15 @@ onMounted(async () => {
} }
}); });
</script> </script>
<style lang="scss">
html, body, #app {
height: 100%;
}
.pageNationGeneral{
display: grid;
grid-template-rows: auto 1fr auto;
height: 100%;
}
</style>
+182 -67
View File
@@ -1,56 +1,88 @@
<template> <template>
<div class="g-thead-tr bg1"> <div
<div class="row g-tr mx-0 gx-1"> class="component-general-list"
<template v-for="itemKey of displayColumns.values()" :key="itemKey"> :style="{
<template v-for="(itemInfo, _dummyIdx) in [headerMap[itemKey]]" :key="_dummyIdx"> height: props.height === 'fill' ? '100%' : props.height === 'static' ? undefined : `${props.height}px`,
<div v-if="itemInfo.subType" class="col d-flex flex-column justify-content-center center"> }"
<div >
v-for="[subItemKey, subItemInfo] of Object.entries(itemInfo.subType)" <AgGridVue
:key="subItemKey" style="width: 100%; height: 100%"
:style="subItemInfo.style ?? itemInfo.style" class="ag-theme-balham-dark"
> :rowHeight="rowHeight"
<template v-if="subItemInfo.subType">Error: nestedType {{ subItemKey }}</template> :columnDefs="columnDefs"
<template v-else>{{ subItemInfo.title }}</template> :rowData="list"
</div> :defaultColDef="defaultColDef"
</div> @grid-ready="onGridReady"
<div v-else class="col align-self-center center" :style="itemInfo.style">{{ itemInfo.title }}</div> />
</template>
</template>
</div>
</div>
<div style="position: relative">
<div v-for="[generalID, general] of list" :key="generalID" class="row g-tbody-tr g-tr mx-0 gx-1">
<template v-for="itemKey of displayColumns.values()" :key="itemKey">
<template v-for="(itemInfo, _dummyIdx) in [headerMap[itemKey]]" :key="_dummyIdx">
<div v-if="itemInfo.subType" class="col d-flex flex-column justify-content-center center">
<template v-for="[subItemKey, subItemInfo] of Object.entries(itemInfo.subType)" :key="subItemKey">
<div v-if="subItemInfo.subType">Error: nestedType {{ subItemKey }}</div>
<template v-else-if="subItemInfo.converter">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :style="subItemInfo.style ?? itemInfo.style" v-html="subItemInfo.converter(general)" />
</template>
<div v-else :style="subItemInfo.style ?? itemInfo.style">
{{general[subItemKey as keyof GeneralListItem]}}
</div>
</template>
</div>
<template v-else-if="itemInfo.converter">
<!-- eslint-disable-next-line vue/no-v-html -->
<div :style="itemInfo.style" class="col align-self-center center" v-html="itemInfo.converter(general)" />
</template>
<div v-else class="col align-self-center center" :style="itemInfo.style">
{{general[itemKey as keyof GeneralListItem]}}
</div>
</template>
</template>
</div>
</div> </div>
</template> </template>
<script lang="ts"></script>
<script lang="ts" setup> <script lang="ts" setup>
import type { GeneralListItem, GeneralListItemP2 } from "@/defs/API/Nation"; import type { GeneralListItem, GeneralListItemP2 } from "@/defs/API/Nation";
import { getIconPath } from "@/util/getIconPath"; import { getIconPath } from "@/util/getIconPath";
import { ref, type PropType, type StyleValue } from "vue"; import { ref, watch, type PropType, type StyleValue } from "vue";
import { AgGridVue } from "ag-grid-vue3";
import type { CellClassParams, CellStyle, ColDef, ColumnApi, GridApi, GridReadyEvent } from "ag-grid-community";
import { getNpcColor } from "@/common_legacy";
const props = defineProps({
list: {
type: Array as PropType<GeneralListItem[]>,
required: true,
},
height: {
type: String as PropType<'static' | 'fill' | number | `${number}px`| `${number}%`>,
required: false,
default: 'static',
},
});
watch(
() => props.list,
() => {
setTimeout(() => {
gridApi.value?.redrawRows();
}, 0);
}
);
watch(
() => props.height,
(val) => {
if (val === 'static') {
gridApi.value?.setDomLayout("autoHeight");
} else {
gridApi.value?.setDomLayout("normal");
}
}
);
const gridApi = ref<GridApi>();
const columnApi = ref<ColumnApi>();
const rowHeight = ref(28);
function onGridReady(params: GridReadyEvent) {
gridApi.value = params.api;
columnApi.value = params.columnApi;
if (props.height === 'static') {
params.api.setDomLayout("autoHeight");
}
else{
params.api.setDomLayout('normal');
}
setRowHeight();
params.api.sizeColumnsToFit();
}
function setRowHeight() {
if (!columnRawDefs.value.icon?.hide) {
rowHeight.value = 64 + 4;
return;
}
rowHeight.value = gridApi.value?.getSizesForCurrentTheme().rowHeight ?? 28;
}
type headerInfo = { type headerInfo = {
title: string; title: string;
@@ -67,14 +99,74 @@ type headerInfo = {
type headerType = type headerType =
| keyof Omit<GeneralListItemP2, "no" | "imgsvr" | "picture" | "lbonus" | "officerLevelText"> | keyof Omit<GeneralListItemP2, "no" | "imgsvr" | "picture" | "lbonus" | "officerLevelText">
| "stat" | "stat"
| "imagePath" | "icon"
| "goldRice" | "goldRice"
| "expDedLv" | "expDedLv"
| "specials" | "specials"
| "killturnAndConnect"; | "killturnAndConnect";
type GenColDef = ColDef & {
field?: headerType;
};
interface GenCellClassParams extends CellClassParams {
data: GeneralListItem;
}
const defaultColDef = ref<ColDef>({
resizable: true,
headerClass: "default-cell-header",
cellClass: "cell-middle",
});
const sortableNumber: GenColDef = {
sortable: true,
comparator: (a, b)=>a-b,
sortingOrder: ['desc', 'asc', null],
filter: 'number'
}
const columnRawDefs = ref<Partial<Record<headerType, GenColDef>>>({
icon: {
headerName: "아이콘",
width: 64 + 24,
suppressSizeToFit: true,
resizable: false,
cellRenderer: (obj: { data: GeneralListItem }) => {
const { data: gen } = obj;
return `<img src="${getIconPath(gen.imgsvr, gen.picture)}" width="64">`;
},
pinned: "left",
lockPosition: true,
},
name: {
headerName: "장수명",
field: "name",
pinned: "left",
sortable: true,
sortingOrder: ['asc', 'desc', null],
lockPosition: true,
cellStyle: (val: GenCellClassParams) => {
const gen = val.data;
const style: StyleValue = {
color: getNpcColor(gen.npc),
};
return style as CellStyle;
},
filter: true,
},
npc: { headerName: "NPC", field: "npc", hide: true },
leadership: { headerName: '통솔', field: 'leadership', ...sortableNumber},
strength: { headerName: '무력', field: 'strength', ...sortableNumber},
intel: { headerName: '지력', field: 'intel', ...sortableNumber},
explevel: { headerName: '명성Lv', field: 'explevel', ...sortableNumber},
dedlevel: { headerName: '계급Lv', field: 'dedlevel', ...sortableNumber},
});
const columnDefs = ref([...Object.values(columnRawDefs.value)]);
const headerMap: Record<headerType, headerInfo> = { const headerMap: Record<headerType, headerInfo> = {
imagePath: { icon: {
title: "아이콘", title: "아이콘",
type: "display", type: "display",
style: { style: {
@@ -155,25 +247,23 @@ const headerMap: Record<headerType, headerInfo> = {
officer_city: { title: "부임지" }, officer_city: { title: "부임지" },
} as const; } as const;
const defaultColumns: headerType[] = [ watch(columnRawDefs, (val) => {
"imagePath", setRowHeight();
"name", columnDefs.value = [...Object.values(val)];
"age",
"stat",
"goldRice",
"expDedLv",
"specials",
"killturnAndConnect",
];
const displayColumns = ref(new Set<headerType>(defaultColumns));
defineProps({
list: {
type: Object as PropType<Map<number, GeneralListItem>>,
required: true,
},
}); });
let currentWidth = -1;
window.addEventListener("resize", (e) => {
setTimeout(() => {
const newWidth = document.querySelector(".container")?.clientWidth ?? -1;
if (currentWidth != newWidth) {
currentWidth = newWidth;
gridApi.value?.sizeColumnsToFit();
}
}, 1);
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -187,3 +277,28 @@ defineProps({
z-index: 5; z-index: 5;
} }
</style> </style>
<style lang="scss">
.component-general-list {
.ag-root-wrapper .cell-middle {
display: flex;
align-items: center;
}
.ag-root-wrapper {
font-family: "Pretendard", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic";
font-size: 14px;
overflow: auto;
}
.ag-root{
overflow: auto;
}
.ag-header{
position: sticky;
top: 0;
z-index: 10;
}
}
</style>
+2
View File
@@ -1,4 +1,6 @@
import "@scss/nationGeneral.scss"; import "@scss/nationGeneral.scss";
import "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham-dark.css";
import { createApp } from 'vue' import { createApp } from 'vue'
import PageNationGeneral from '@/PageNationGeneral.vue'; import PageNationGeneral from '@/PageNationGeneral.vue';
+30
View File
@@ -37,6 +37,8 @@
"@typescript-eslint/parser": "^5.14.0", "@typescript-eslint/parser": "^5.14.0",
"@vue/compiler-sfc": "^3.2.31", "@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"ag-grid-community": "^27.1.0",
"ag-grid-vue3": "^27.1.0",
"async-validator": "^4.0.7", "async-validator": "^4.0.7",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"axios": "^0.26.1", "axios": "^0.26.1",
@@ -3066,6 +3068,20 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/ag-grid-community": {
"version": "27.1.0",
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-27.1.0.tgz",
"integrity": "sha512-SWzIJTNa7C6Vinizelcoc1FAJQRt1pDn+A8XHQDO2GTQT+VjBnPL8fg94fLJy0EEvqaN5IhDybNS0nD07SKIQw=="
},
"node_modules/ag-grid-vue3": {
"version": "27.1.0",
"resolved": "https://registry.npmjs.org/ag-grid-vue3/-/ag-grid-vue3-27.1.0.tgz",
"integrity": "sha512-3kZCK4UiFc/jCGvMy7zXjNG130GLuLboqoo4jzdLnD9PNR4hzqYjKVnCFHAQVi9Zj3hRBAWDwEU18MHukZuwKA==",
"dependencies": {
"ag-grid-community": "~27.1.0",
"vue": "^3.0.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -12796,6 +12812,20 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.1.1.tgz",
"integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w==" "integrity": "sha512-FbJdceMlPHEAWJOILDk1fXD8lnTlEIWFkqtfk+MvmL5q/qlHfN7GEHcsFZWt/Tea9jRNPWUZG4G976nqAAmU9w=="
}, },
"ag-grid-community": {
"version": "27.1.0",
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-27.1.0.tgz",
"integrity": "sha512-SWzIJTNa7C6Vinizelcoc1FAJQRt1pDn+A8XHQDO2GTQT+VjBnPL8fg94fLJy0EEvqaN5IhDybNS0nD07SKIQw=="
},
"ag-grid-vue3": {
"version": "27.1.0",
"resolved": "https://registry.npmjs.org/ag-grid-vue3/-/ag-grid-vue3-27.1.0.tgz",
"integrity": "sha512-3kZCK4UiFc/jCGvMy7zXjNG130GLuLboqoo4jzdLnD9PNR4hzqYjKVnCFHAQVi9Zj3hRBAWDwEU18MHukZuwKA==",
"requires": {
"ag-grid-community": "~27.1.0",
"vue": "^3.0.0"
}
},
"ajv": { "ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+2
View File
@@ -50,6 +50,8 @@
"@typescript-eslint/parser": "^5.14.0", "@typescript-eslint/parser": "^5.14.0",
"@vue/compiler-sfc": "^3.2.31", "@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-typescript": "^10.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"ag-grid-community": "^27.1.0",
"ag-grid-vue3": "^27.1.0",
"async-validator": "^4.0.7", "async-validator": "^4.0.7",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"axios": "^0.26.1", "axios": "^0.26.1",