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>
<BContainer id="container" :toast="{ root: true }" class="pageNationGeneral bg0">
<TopBackBar :title="title" />
<GeneralList :list="generalList" />
<GeneralList :list="generalList" :height="'fill'" />
<BottomBar />
</BContainer>
</template>
@@ -23,7 +23,7 @@ import { merge2DArrToObjectArr } from "./util/merge2DArrToObjectArr";
import type { GeneralListItem } from "./defs/API/Nation";
import GeneralList from "./components/GeneralList.vue";
const generalList = ref(new Map<number, GeneralListItem>());
const generalList = ref<GeneralListItem[]>([]);
const title = "세력 장수";
@@ -33,13 +33,13 @@ onMounted(async () => {
//XXX: 로직상 똑같은데....
if (permission == 0) {
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) {
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)) {
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 {
throw `?? ${permission}`;
}
@@ -50,3 +50,15 @@ onMounted(async () => {
}
});
</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>
<div class="g-thead-tr bg1">
<div class="row 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">
<div
v-for="[subItemKey, subItemInfo] of Object.entries(itemInfo.subType)"
:key="subItemKey"
:style="subItemInfo.style ?? itemInfo.style"
>
<template v-if="subItemInfo.subType">Error: nestedType {{ subItemKey }}</template>
<template v-else>{{ subItemInfo.title }}</template>
</div>
</div>
<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
class="component-general-list"
:style="{
height: props.height === 'fill' ? '100%' : props.height === 'static' ? undefined : `${props.height}px`,
}"
>
<AgGridVue
style="width: 100%; height: 100%"
class="ag-theme-balham-dark"
:rowHeight="rowHeight"
:columnDefs="columnDefs"
:rowData="list"
:defaultColDef="defaultColDef"
@grid-ready="onGridReady"
/>
</div>
</template>
<script lang="ts"></script>
<script lang="ts" setup>
import type { GeneralListItem, GeneralListItemP2 } from "@/defs/API/Nation";
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 = {
title: string;
@@ -67,14 +99,74 @@ type headerInfo = {
type headerType =
| keyof Omit<GeneralListItemP2, "no" | "imgsvr" | "picture" | "lbonus" | "officerLevelText">
| "stat"
| "imagePath"
| "icon"
| "goldRice"
| "expDedLv"
| "specials"
| "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> = {
imagePath: {
icon: {
title: "아이콘",
type: "display",
style: {
@@ -155,25 +247,23 @@ const headerMap: Record<headerType, headerInfo> = {
officer_city: { title: "부임지" },
} as const;
const defaultColumns: headerType[] = [
"imagePath",
"name",
"age",
"stat",
"goldRice",
"expDedLv",
"specials",
"killturnAndConnect",
];
const displayColumns = ref(new Set<headerType>(defaultColumns));
defineProps({
list: {
type: Object as PropType<Map<number, GeneralListItem>>,
required: true,
},
watch(columnRawDefs, (val) => {
setRowHeight();
columnDefs.value = [...Object.values(val)];
});
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>
<style scoped lang="scss">
@@ -187,3 +277,28 @@ defineProps({
z-index: 5;
}
</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 "ag-grid-community/dist/styles/ag-grid.css";
import "ag-grid-community/dist/styles/ag-theme-balham-dark.css";
import { createApp } from 'vue'
import PageNationGeneral from '@/PageNationGeneral.vue';
+30
View File
@@ -37,6 +37,8 @@
"@typescript-eslint/parser": "^5.14.0",
"@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-typescript": "^10.0.0",
"ag-grid-community": "^27.1.0",
"ag-grid-vue3": "^27.1.0",
"async-validator": "^4.0.7",
"autoprefixer": "^10.4.2",
"axios": "^0.26.1",
@@ -3066,6 +3068,20 @@
"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": {
"version": "6.12.6",
"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",
"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": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+2
View File
@@ -50,6 +50,8 @@
"@typescript-eslint/parser": "^5.14.0",
"@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-typescript": "^10.0.0",
"ag-grid-community": "^27.1.0",
"ag-grid-vue3": "^27.1.0",
"async-validator": "^4.0.7",
"autoprefixer": "^10.4.2",
"axios": "^0.26.1",