feat(wip): 장수패널에 ag-grid
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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';
|
||||
|
||||
Generated
+30
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user