f3ddd4d84b
Fixed 3 critical bugs in grid editing: 1. onCellValueChanged redrawRows() removal - Removed event.api.redrawRows() that was resetting cell input - Issue: redrawRows() triggered computed property re-evaluation - Result: Array reference changed, grid lost input value 2. ScrollApiModule registration - Added ScrollApiModule to ModuleRegistry - Issue: focusRow() called ensureIndexVisible without module - Result: AG Grid #200 error, page hung 3. onCellEditingStopped removal - Removed auto-restart of edit mode on cell exit - Issue: Prevented navigateToNextCell from working on Enter key - Result: Enter key now properly moves focus to next cell 4. CommonCodeManagementPage focusRow safety - Wrapped focusRow() in try-catch - Issue: focusRow may not be available, causing errors - Result: Grid continues even if focusRow unavailable Affects: /system/common-codes grid editing and all pages using KsDataGrid Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
499 lines
18 KiB
Vue
499 lines
18 KiB
Vue
<script setup lang="ts">
|
||
import { ref, computed, reactive, nextTick } from 'vue'
|
||
import SearchListCrudPage from '../../../shared/ui/screen-types/v2/SearchListCrudPage.vue'
|
||
import DataGridShell from '../../../shared/ui/DataGridShell.vue'
|
||
import {
|
||
KsButton,
|
||
KsSelect,
|
||
KsStatusTag,
|
||
KsTextField,
|
||
KsFormSection
|
||
} from '../../../shared/ui/components'
|
||
|
||
import type { UiGridColumn, UiSelectOption } from '../../../shared/ui/adapter/contracts'
|
||
import type { StandardScreenState } from '../../../shared/ui/contracts/screenContract'
|
||
|
||
const screenState = ref<StandardScreenState>('READY')
|
||
const evidence = reactive({
|
||
asOf: new Date().toISOString(),
|
||
version: 'v60-SYS-01-Contract',
|
||
})
|
||
|
||
// Search & Filter State
|
||
const searchGroupQuery = ref('')
|
||
const searchUseYnFilter = ref('ALL')
|
||
|
||
const useYnOptions: UiSelectOption[] = [
|
||
{ label: '전체 상태', value: 'ALL' },
|
||
{ label: '사용 (Y)', value: 'Y' },
|
||
{ label: '미사용 (N)', value: 'N' },
|
||
]
|
||
|
||
// Mock Group Codes Data
|
||
interface GroupCode {
|
||
groupCode: string
|
||
groupName: string
|
||
isSystem: boolean
|
||
useYn: 'Y' | 'N'
|
||
sortOrder: number
|
||
codeCount: number
|
||
description: string
|
||
_isNew?: boolean
|
||
_isModified?: boolean
|
||
}
|
||
|
||
interface CommonCode {
|
||
code: string
|
||
codeName: string
|
||
value1: string
|
||
value2: string
|
||
sortOrder: number
|
||
useYn: 'Y' | 'N'
|
||
description: string
|
||
_isNew?: boolean
|
||
_isModified?: boolean
|
||
}
|
||
|
||
const mockGroupCodes = ref<GroupCode[]>([
|
||
{ groupCode: 'SYS_ROLE', groupName: '시스템 사용자 권한', isSystem: true, useYn: 'Y', sortOrder: 1, codeCount: 4, description: '시스템 관리자, 트레이더, 리서처, 데이터관리자 권한 코드' },
|
||
{ groupCode: 'ORDER_STATUS', groupName: 'OMS 주문 처리 상태', isSystem: true, useYn: 'Y', sortOrder: 2, codeCount: 5, description: 'OMS 주문 수신, 검증, 체결 완료, 거부, 취소 상태' },
|
||
{ groupCode: 'ASSET_TYPE', groupName: '포트폴리오 자산 유형', isSystem: false, useYn: 'Y', sortOrder: 3, codeCount: 3, description: '주식, ETF, 현금성 자산 구분' },
|
||
{ groupCode: 'REBALANCE_TYPE', groupName: '리밸런싱 실행 유형', isSystem: false, useYn: 'Y', sortOrder: 4, codeCount: 3, description: '정기 리밸런싱, 이탈 임계값 조절, 손절매 리밸런싱' },
|
||
{ groupCode: 'DQ_STATUS', groupName: '데이터 품질 검증 상태', isSystem: true, useYn: 'Y', sortOrder: 5, codeCount: 3, description: 'DQ 통과(PASSED), 격리(QUARANTINED), 대기(PENDING)' },
|
||
])
|
||
|
||
const mockChildCodesMap = reactive<Record<string, CommonCode[]>>({
|
||
SYS_ROLE: [
|
||
{ code: 'ADMIN', codeName: '시스템 관리자 (Admin)', value1: 'ALL_ACCESS', value2: 'ROLE_ADMIN', sortOrder: 1, useYn: 'Y', description: '모든 시스템 관리 및 실행 권한' },
|
||
{ code: 'TRADER', codeName: '트레이더 (Trader)', value1: 'OMS_EXECUTE', value2: 'ROLE_TRADER', sortOrder: 2, useYn: 'Y', description: '리밸런싱 및 주문 제출 권한' },
|
||
{ code: 'RESEARCHER', codeName: '퀀트 리서처 (Researcher)', value1: 'MODEL_EVAL', value2: 'ROLE_QUANT', sortOrder: 3, useYn: 'Y', description: '매도 정책 시뮬레이션 권한' },
|
||
{ code: 'DATA_ADMIN', codeName: '데이터 관리자 (DataAdmin)', value1: 'INGEST_OPS', value2: 'ROLE_DATA', sortOrder: 4, useYn: 'Y', description: '시장 데이터 수집 배치 관리' },
|
||
],
|
||
ORDER_STATUS: [
|
||
{ code: 'QUEUED', codeName: '대기 중 (Queued)', value1: '10', value2: 'INIT', sortOrder: 1, useYn: 'Y', description: 'OMS 대기열 등록 완료' },
|
||
{ code: 'SUBMITTED', codeName: '주문 제출 (Submitted)', value1: '20', value2: 'SENT', sortOrder: 2, useYn: 'Y', description: '증권사 KIS API 제출' },
|
||
{ code: 'EXECUTED', codeName: '체결 완료 (Executed)', value1: '30', value2: 'DONE', sortOrder: 3, useYn: 'Y', description: '전량 체결 완료' },
|
||
{ code: 'PARTIAL', codeName: '부분 체결 (Partial)', value1: '35', value2: 'PART', sortOrder: 4, useYn: 'Y', description: '일부 잔량 남음' },
|
||
{ code: 'REJECTED', codeName: '주문 거부 (Rejected)', value1: '90', value2: 'ERR', sortOrder: 5, useYn: 'Y', description: '이탈 한도 초과 또는 오류' },
|
||
],
|
||
ASSET_TYPE: [
|
||
{ code: 'STOCK', codeName: '개별 주식 (Stock)', value1: 'EQ', value2: 'EQUITY', sortOrder: 1, useYn: 'Y', description: '국내외 개별 상장 주식' },
|
||
{ code: 'ETF', codeName: '상장지수펀드 (ETF)', value1: 'FUND', value2: 'INDEX', sortOrder: 2, useYn: 'Y', description: '지수 및 테마 ETF' },
|
||
{ code: 'CASH', codeName: '현금성 자산 (Cash)', value1: 'MONEY', value2: 'USD', sortOrder: 3, useYn: 'Y', description: '예수금 및 단기 채권' },
|
||
],
|
||
REBALANCE_TYPE: [
|
||
{ code: 'PERIODIC', codeName: '정기 리밸런싱', value1: 'MONTHLY', value2: 'CALENDAR', sortOrder: 1, useYn: 'Y', description: '월간/분기 정기 주기 스케줄' },
|
||
{ code: 'DRIFT_TRIGGER', codeName: '임계값 이탈 리밸런싱', value1: 'BAND', value2: 'THRESHOLD', sortOrder: 2, useYn: 'Y', description: '목표 비중 3% 초과 이탈 시' },
|
||
{ code: 'STOP_LOSS', codeName: '손절매 리밸런싱', value1: 'RISK', value2: 'EXIT', sortOrder: 3, useYn: 'Y', description: '리스크 한도 초과 매도' },
|
||
],
|
||
DQ_STATUS: [
|
||
{ code: 'PASSED', codeName: '정상 통과 (Passed)', value1: 'PASS', value2: 'OK', sortOrder: 1, useYn: 'Y', description: '결함률 0% 무결성 유지' },
|
||
{ code: 'QUARANTINED', codeName: '격리 조치 (Quarantined)', value1: 'BLOCK', value2: 'HOLD', sortOrder: 2, useYn: 'Y', description: '결함 발견으로 알고리즘 차단' },
|
||
{ code: 'PENDING', codeName: '검증 대기 (Pending)', value1: 'WAIT', value2: 'CALC', sortOrder: 3, useYn: 'Y', description: '배치 검증 수행 대기 중' },
|
||
]
|
||
})
|
||
|
||
// Selected Master & Detail State
|
||
const selectedGroupCode = ref<GroupCode>(mockGroupCodes.value[0])
|
||
|
||
// Active Search State (Applied on [🔍 조회] click or Enter key)
|
||
const activeSearchQuery = ref('')
|
||
const activeUseYnFilter = ref('ALL')
|
||
|
||
const handleGroupSearch = () => {
|
||
activeSearchQuery.value = searchGroupQuery.value.trim()
|
||
activeUseYnFilter.value = searchUseYnFilter.value
|
||
|
||
nextTick(() => {
|
||
if (filteredGroupCodes.value.length > 0) {
|
||
selectedGroupCode.value = filteredGroupCodes.value[0]
|
||
}
|
||
masterGridRef.value?.redrawRows()
|
||
})
|
||
}
|
||
|
||
|
||
const filteredGroupCodes = computed(() => {
|
||
const query = (activeSearchQuery.value || searchGroupQuery.value).trim().toLowerCase()
|
||
const useYn = activeUseYnFilter.value || searchUseYnFilter.value
|
||
|
||
return mockGroupCodes.value.filter(g => {
|
||
const matchesQuery = !query ||
|
||
g.groupCode.toLowerCase().includes(query) ||
|
||
g.groupName.toLowerCase().includes(query)
|
||
const matchesUseYn = useYn === 'ALL' || g.useYn === useYn
|
||
return matchesQuery && matchesUseYn
|
||
})
|
||
})
|
||
|
||
|
||
|
||
const currentChildCodes = computed(() => {
|
||
if (!selectedGroupCode.value) return []
|
||
return mockChildCodesMap[selectedGroupCode.value.groupCode] || []
|
||
})
|
||
|
||
// Grids Columns (Auto-sized based on Math.max(header, content), capped by minWidth & maxWidth)
|
||
const groupColumns: UiGridColumn[] = [
|
||
{ field: 'groupCode', header: '그룹코드 ID', minWidth: 120, maxWidth: 220, flex: 2, editable: true },
|
||
{ field: 'groupName', header: '그룹명', minWidth: 140, maxWidth: 300, flex: 3, editable: true },
|
||
{ field: 'useYn', header: '사용여부', minWidth: 70, maxWidth: 100, flex: 1, editable: true },
|
||
{ field: 'codeCount', header: '코드 수', minWidth: 65, maxWidth: 110, flex: 1, formatter: v => `${v}개` }
|
||
]
|
||
|
||
const childColumns: UiGridColumn[] = [
|
||
{ field: 'code', header: '공통코드 ID', minWidth: 110, maxWidth: 200, flex: 2, editable: true },
|
||
{ field: 'codeName', header: '코드명', minWidth: 140, maxWidth: 320, flex: 3, editable: true },
|
||
{ field: 'value1', header: '속성값 1', minWidth: 85, maxWidth: 180, flex: 2, editable: true },
|
||
{ field: 'value2', header: '속성값 2', minWidth: 85, maxWidth: 180, flex: 2, editable: true },
|
||
{ field: 'sortOrder', header: '정렬', minWidth: 60, maxWidth: 90, flex: 1, editable: true },
|
||
{ field: 'useYn', header: '사용여부', minWidth: 70, maxWidth: 100, flex: 1, editable: true }
|
||
]
|
||
|
||
|
||
const masterGridRef = ref<{ focusRow: (rowIndex: number, colKey?: string) => void; redrawRows: () => void } | null>(null)
|
||
const detailGridRef = ref<{ focusRow: (rowIndex: number, colKey?: string) => void; redrawRows: () => void } | null>(null)
|
||
|
||
const selectGroup = (group: GroupCode) => {
|
||
selectedGroupCode.value = group
|
||
}
|
||
|
||
// Top-Row Insertion Pattern (unshift / Index 0)
|
||
const addGroupRow = () => {
|
||
searchGroupQuery.value = ''
|
||
searchUseYnFilter.value = 'ALL'
|
||
activeSearchQuery.value = ''
|
||
activeUseYnFilter.value = 'ALL'
|
||
|
||
const newGrp: GroupCode = {
|
||
groupCode: `NEW_GRP_${mockGroupCodes.value.length + 1}`,
|
||
groupName: '새 그룹명',
|
||
isSystem: false,
|
||
useYn: 'Y',
|
||
sortOrder: 1,
|
||
codeCount: 0,
|
||
description: '그리드 최상단 신규 등록 그룹',
|
||
_isNew: true
|
||
}
|
||
|
||
mockGroupCodes.value.unshift(newGrp)
|
||
selectedGroupCode.value = newGrp
|
||
|
||
nextTick(() => {
|
||
masterGridRef.value?.focusRow(0, 'groupCode')
|
||
})
|
||
}
|
||
|
||
|
||
const removeGroupRow = () => {
|
||
if (!selectedGroupCode.value) {
|
||
alert('삭제 또는 취소할 그룹코드를 선택하세요.')
|
||
return
|
||
}
|
||
const idx = mockGroupCodes.value.findIndex(g => g.groupCode === selectedGroupCode.value.groupCode)
|
||
if (idx === -1) return
|
||
|
||
const target = mockGroupCodes.value[idx]
|
||
if (target._isNew) {
|
||
mockGroupCodes.value.splice(idx, 1)
|
||
if (mockGroupCodes.value.length > 0) {
|
||
selectedGroupCode.value = mockGroupCodes.value[0]
|
||
}
|
||
masterGridRef.value?.redrawRows()
|
||
return
|
||
}
|
||
|
||
if (target.isSystem) {
|
||
alert('시스템 SYSTEM 필수 그룹코드는 삭제할 수 없습니다.')
|
||
return
|
||
}
|
||
|
||
if (confirm(`선택한 마스터 그룹코드 [${target.groupCode}] (${target.groupName})를 삭제하시겠습니까?`)) {
|
||
delete mockChildCodesMap[target.groupCode]
|
||
mockGroupCodes.value.splice(idx, 1)
|
||
if (mockGroupCodes.value.length > 0) {
|
||
selectedGroupCode.value = mockGroupCodes.value[0]
|
||
}
|
||
masterGridRef.value?.redrawRows()
|
||
}
|
||
}
|
||
|
||
const saveMasterBatch = () => {
|
||
mockGroupCodes.value = mockGroupCodes.value.map(row => {
|
||
const copy = { ...row }
|
||
delete copy._isNew
|
||
delete copy._isModified
|
||
return copy
|
||
})
|
||
|
||
nextTick(() => {
|
||
masterGridRef.value?.redrawRows()
|
||
alert(`마스터 그룹코드 ${mockGroupCodes.value.length}건이 성공적으로 저장되었습니다.`)
|
||
})
|
||
}
|
||
|
||
|
||
// Inline Child Code Grid Editing Handlers (Top-Row Insertion)
|
||
const addGridRow = () => {
|
||
const groupCodeKey = selectedGroupCode.value.groupCode
|
||
if (!mockChildCodesMap[groupCodeKey]) {
|
||
mockChildCodesMap[groupCodeKey] = reactive<CommonCode[]>([])
|
||
}
|
||
const list = mockChildCodesMap[groupCodeKey]
|
||
|
||
const newRow: CommonCode = {
|
||
code: `NEW_CODE_${list.length + 1}`,
|
||
codeName: '새 코드명',
|
||
value1: '',
|
||
value2: '',
|
||
sortOrder: 1,
|
||
useYn: 'Y',
|
||
description: '그리드 최상단 신규 등록 코드',
|
||
_isNew: true
|
||
}
|
||
|
||
list.unshift(newRow)
|
||
selectedGroupCode.value.codeCount = list.length
|
||
|
||
nextTick(() => {
|
||
try {
|
||
detailGridRef.value?.focusRow?.(0, 'code')
|
||
} catch (e) {
|
||
console.warn('focusRow not available', e)
|
||
}
|
||
})
|
||
}
|
||
|
||
const removeGridRow = () => {
|
||
const groupCodeKey = selectedGroupCode.value.groupCode
|
||
const list = mockChildCodesMap[groupCodeKey] || []
|
||
if (list.length === 0) {
|
||
alert('삭제할 세부 코드가 없습니다.')
|
||
return
|
||
}
|
||
|
||
const target = list[0]
|
||
|
||
if (target._isNew) {
|
||
list.shift()
|
||
selectedGroupCode.value.codeCount = list.length
|
||
detailGridRef.value?.redrawRows()
|
||
return
|
||
}
|
||
|
||
if (confirm(`[${groupCodeKey}] 그룹의 세부 코드 [${target.code}] (${target.codeName})를 삭제하시겠습니까?`)) {
|
||
list.shift()
|
||
selectedGroupCode.value.codeCount = list.length
|
||
detailGridRef.value?.redrawRows()
|
||
}
|
||
}
|
||
|
||
const saveGridBatch = () => {
|
||
if (!selectedGroupCode.value) return
|
||
const groupCodeKey = selectedGroupCode.value.groupCode
|
||
const list = mockChildCodesMap[groupCodeKey] || []
|
||
|
||
// Save both master row changes and detail child code rows cleanly
|
||
mockGroupCodes.value.forEach(row => {
|
||
delete row._isNew
|
||
delete row._isModified
|
||
})
|
||
|
||
list.forEach(row => {
|
||
delete row._isNew
|
||
delete row._isModified
|
||
})
|
||
|
||
selectedGroupCode.value.codeCount = list.length
|
||
masterGridRef.value?.redrawRows()
|
||
detailGridRef.value?.redrawRows()
|
||
|
||
alert(`[${groupCodeKey}] 그룹 세부 공통코드 ${list.length}건 및 마스터 코드 변경사항이 성공적으로 저장되었습니다.`)
|
||
}
|
||
|
||
|
||
const handleClosePage = () => {
|
||
if (confirm('현재 화면을 닫고 메인 워크스페이스로 이동하시겠습니까?')) {
|
||
window.history.back()
|
||
}
|
||
}
|
||
|
||
const handleRetry = () => {
|
||
screenState.value = 'READY'
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<SearchListCrudPage
|
||
title="시스템 공통코드 관리 (전면 그리드 인라인 편집)"
|
||
subtitle="마스터 그룹코드와 세부 공통코드 모두 그리드 셀 직접 편집 및 행 추가/일괄 저장 방식으로 신속하게 관리합니다."
|
||
:state="screenState"
|
||
:evidence="evidence"
|
||
:initial-ratio="50"
|
||
storage-key="ks_sys_common_code_splitter_v50"
|
||
@retry="handleRetry"
|
||
>
|
||
|
||
<!-- Dedicated KBX Page Command Bar Slot (Right-aligned, with Close button) -->
|
||
<template #commandBar>
|
||
<KsButton label="🔍 조회" variant="primary" @click="handleGroupSearch" />
|
||
<KsButton label="💾 저장" variant="primary" @click="saveGridBatch" />
|
||
<KsButton label="❌ 닫기" variant="secondary" @click="handleClosePage" />
|
||
</template>
|
||
|
||
|
||
|
||
<!-- Page Level Actions Header Slot -->
|
||
<template #actions>
|
||
<KsButton label="📤 엑셀 다운로드" variant="secondary" />
|
||
</template>
|
||
|
||
|
||
<!-- Master Content Area (Left: Group Code Grid Inline Editor & Search Filters bound together) -->
|
||
<div class="ks-master-pane">
|
||
<KsFormSection title="1. 마스터 그룹코드 그리드 (Inline Editor)">
|
||
<template #actions>
|
||
<KsButton label="➕ 그룹 행 추가" variant="secondary" @click="addGroupRow" />
|
||
<KsButton label="🗑️ 그룹 행 삭제" variant="danger" @click="removeGroupRow" />
|
||
<KsButton label="💾 그룹 일괄 저장" variant="secondary" @click="saveMasterBatch" />
|
||
</template>
|
||
|
||
<div class="ks-filter-bar">
|
||
<KsTextField
|
||
v-model="searchGroupQuery"
|
||
label="그룹코드 검색"
|
||
placeholder="그룹코드 ID 또는 그룹명..."
|
||
class="ks-set-search"
|
||
@keyup.enter="handleGroupSearch"
|
||
/>
|
||
<KsSelect
|
||
v-model="searchUseYnFilter"
|
||
label="사용 여부"
|
||
:options="useYnOptions"
|
||
class="ks-set-select"
|
||
/>
|
||
</div>
|
||
|
||
|
||
|
||
<DataGridShell
|
||
ref="masterGridRef"
|
||
:rows="filteredGroupCodes"
|
||
:columns="groupColumns"
|
||
empty-message="조회 조건에 해당하는 그룹코드가 없습니다."
|
||
height="100%"
|
||
@row-selected="selectGroup"
|
||
/>
|
||
<div class="group-select-bar">
|
||
<span>선택된 그룹: <strong>[{{ selectedGroupCode.groupCode }}] {{ selectedGroupCode.groupName }}</strong></span>
|
||
<KsStatusTag :value="selectedGroupCode.isSystem ? '시스템 SYSTEM' : '일반 USER'" :severity="selectedGroupCode.isSystem ? 'warning' : 'info'" />
|
||
</div>
|
||
</KsFormSection>
|
||
</div>
|
||
|
||
<!-- Detail Pane (Right: Inline Editable Child Code Grid Workstation) -->
|
||
<template #detail>
|
||
<div class="ks-detail-pane">
|
||
<!-- Inline Child Code Grid Section -->
|
||
<KsFormSection
|
||
:title="`2. [${selectedGroupCode.groupCode}] 세부 공통코드 그리드 (Inline Editor)`"
|
||
description="셀을 더블클릭하여 즉시 수정 후 상단 [💾 세부 코드 일괄 저장] 버튼을 누르세요."
|
||
>
|
||
<template #actions>
|
||
<KsButton label="➕ 행 추가" variant="secondary" @click="addGridRow" />
|
||
<KsButton label="🗑️ 행 삭제" variant="danger" @click="removeGridRow" />
|
||
<KsButton label="💾 세부 코드 일괄 저장" variant="primary" @click="saveGridBatch" />
|
||
</template>
|
||
|
||
<DataGridShell
|
||
ref="detailGridRef"
|
||
:rows="currentChildCodes"
|
||
:columns="childColumns"
|
||
empty-message="그룹코드에 속한 세부 코드가 없습니다. ➕ 행 추가로 등록하세요."
|
||
height="100%"
|
||
/>
|
||
</KsFormSection>
|
||
</div>
|
||
</template>
|
||
</SearchListCrudPage>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.ks-master-pane,
|
||
.ks-detail-pane {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--ks-space-2);
|
||
height: 100%;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
|
||
.ks-filter-bar {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
gap: var(--ks-space-3);
|
||
padding: 6px 10px;
|
||
background: var(--ks-color-surface);
|
||
border: 1px solid var(--ks-color-border);
|
||
border-radius: var(--ks-radius-sm);
|
||
margin-bottom: 6px;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.ks-filter-bar :deep(.ks-text-field),
|
||
.ks-filter-bar :deep(.ks-select) {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 6px;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.ks-filter-bar :deep(.ks-text-field__label),
|
||
.ks-filter-bar :deep(.ks-select__label) {
|
||
margin-bottom: 0;
|
||
white-space: nowrap;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.ks-filter-bar :deep(.ks-text-field__container),
|
||
.ks-filter-bar :deep(.ks-select__container) {
|
||
flex: 1;
|
||
}
|
||
|
||
.ks-set-search {
|
||
flex: 2;
|
||
min-width: 150px;
|
||
}
|
||
|
||
.ks-set-select {
|
||
flex: 1;
|
||
min-width: 110px;
|
||
}
|
||
|
||
.ks-search-btn {
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
|
||
.group-select-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 6px 10px;
|
||
background: var(--ks-color-canvas);
|
||
border: 1px solid var(--ks-color-border);
|
||
border-radius: var(--ks-radius-sm);
|
||
font-size: var(--ks-font-body);
|
||
margin-top: 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
</style>
|