Commit 7f195dc7 authored by shj's avatar shj

[IMPLEMENT] 인덱스 관리 dialog 구현

parent 68d538d8
...@@ -23,6 +23,12 @@ public class GuideTestController { ...@@ -23,6 +23,12 @@ public class GuideTestController {
GuideService guideService; GuideService guideService;
GuideTestService guideTestService; GuideTestService guideTestService;
@GetMapping
public ResponseEntity<?> getGuide(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideTestService.findGuideById(guideId), HttpStatus.OK);
}
@ApiOperation(value="모든 가이드 목차 조회") @ApiOperation(value="모든 가이드 목차 조회")
@GetMapping("/index") @GetMapping("/index")
public ResponseEntity<?> findIndex()throws Exception{ public ResponseEntity<?> findIndex()throws Exception{
...@@ -45,8 +51,8 @@ public class GuideTestController { ...@@ -45,8 +51,8 @@ public class GuideTestController {
} }
@PutMapping("/index") @PutMapping("/index")
public ResponseEntity<?> updateGuideIndex(@RequestBody Guide guide) throws Exception{ public ResponseEntity<?> updateGuideIndex(@RequestBody GuideTest guide) throws Exception{
return new ResponseEntity<>(guideService.updateGuide(guide), HttpStatus.OK); return new ResponseEntity<>(guideTestService.updateGuide(guide), HttpStatus.OK);
} }
...@@ -56,16 +62,10 @@ public class GuideTestController { ...@@ -56,16 +62,10 @@ public class GuideTestController {
} }
@GetMapping
public ResponseEntity<?> getGuide(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideTestService.findGuideById(guideId), HttpStatus.OK);
}
@DeleteMapping @DeleteMapping
public ResponseEntity<?> deleteGuideIndex(@RequestParam(value = "guideId") String guideId, @RequestParam(value = "cascade", required = false) boolean cascade) throws Exception{ public ResponseEntity<?> deleteGuideIndex(@RequestParam(value = "guideId") String guideId) throws Exception{
return new ResponseEntity<>(guideService.deleteGuide(guideId, cascade), HttpStatus.OK); return new ResponseEntity<>(guideTestService.deleteGuide(guideId), HttpStatus.OK);
} }
@PutMapping @PutMapping
......
package com.vazil.vridge.docs.service; package com.vazil.vridge.docs.service;
import com.vazil.vridge.docs.model.Guide;
import com.vazil.vridge.docs.model.GuideTest; import com.vazil.vridge.docs.model.GuideTest;
import com.vazil.vridge.docs.repository.GuideTestRepository; import com.vazil.vridge.docs.repository.GuideTestRepository;
import com.vazil.vridge.docs.utils.TimeManager; import com.vazil.vridge.docs.utils.TimeManager;
...@@ -100,6 +101,10 @@ public class GuideTestService { ...@@ -100,6 +101,10 @@ public class GuideTestService {
@Transactional @Transactional
public GuideTest createGuide(GuideTest guide) throws Exception{ public GuideTest createGuide(GuideTest guide) throws Exception{
// guide.setOrder(guide.getOrder() == null ? getLastOrder(guide.getParentId()) : 0); // guide.setOrder(guide.getOrder() == null ? getLastOrder(guide.getParentId()) : 0);
// todo : 기존 인덱스와 위치가 중복될 경우, 기존 인덱스들 order + 1
guide.setLike(0); guide.setLike(0);
guide.setView(0); guide.setView(0);
guide.setCreateDate(TimeManager.now()); guide.setCreateDate(TimeManager.now());
...@@ -108,11 +113,24 @@ public class GuideTestService { ...@@ -108,11 +113,24 @@ public class GuideTestService {
@Transactional @Transactional
public GuideTest updateGuide(GuideTest guide) throws Exception{ public GuideTest updateGuide(GuideTest guide) throws Exception{
GuideTest savedGuide = repository.findById(guide.getId()).orElseThrow(NoSuchElementException::new); GuideTest db = repository.findById(guide.getId()).orElseThrow(NoSuchElementException::new);
// todo : 기존 인덱스와 위치가 중복될 경우, 기존 인덱스들 order + 1
savedGuide.setOrder(guide.getOrder());
savedGuide.setUpdateDate(TimeManager.now());
return repository.save(savedGuide); db.setLocale(guide.getLocale());
db.setTitle(guide.getTitle());
db.setOrder(guide.getOrder());
db.setPath(guide.getPath());
db.setDepth(guide.getDepth());
return repository.save(db);
}
@Transactional
public String deleteGuide(String guideId) throws Exception{
GuideTest db = repository.findById(guideId).orElseThrow(NoSuchElementException::new);
repository.delete(db);
return db.getId();
} }
} }
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
padding-right: 240px; padding-right: 240px;
} }
.guide-wrap { .guide-wrap {
min-height: 100vh !important; min-height: 90vh !important;
max-width: 1280px !important; max-width: 1280px !important;
margin:0 auto; margin:0 auto;
margin-top:36px; margin-top:36px;
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
} }
.guide-content{ .guide-content{
margin-top:200px; margin-top:130px;
width:100% !important; width:100% !important;
h2{ h2{
......
...@@ -8,10 +8,10 @@ ...@@ -8,10 +8,10 @@
> >
<v-list-item <v-list-item
v-if="item.children.length === 0" v-if="item.children.length === 0"
@click="clickChildEvent(item)" @click="clickItemEvent(item)"
:ripple="false" :ripple="false"
:class="isActiveMenu(item.id) ? 'active' : ''" :style="{'border-left': depth !== 1 && '1px solid rgba(0,0,0,0.15)'}"
:style="isActiveMenu(item.id) ? 'border-left: 2px solid #1E88E5;' : 'border-left: 1px solid rgba(0,0,0,0.15);'" :class="selectedItem === item ? 'active' : ''"
> >
<v-list-item-title v-text="item.title" /> <v-list-item-title v-text="item.title" />
</v-list-item> </v-list-item>
...@@ -20,9 +20,9 @@ ...@@ -20,9 +20,9 @@
v-else v-else
v-model="item.active" v-model="item.active"
:ripple="false" :ripple="false"
:class="isActiveMenu(item.id) ? 'active' : ''"
:sub-group="depth > 1 ? true : false" :sub-group="depth > 1 ? true : false"
:style="isActiveMenu(item.id) ? 'border-left: 2px solid #1E88E5;' : 'border-left: 1px solid rgba(0,0,0,0.15);'" :class="selectedItem === item ? 'active' : ''"
:style="{'border-left': depth !== 1 && '1px solid rgba(0,0,0,0.15)'}"
prepend-icon="" prepend-icon=""
> >
<template v-slot:appendIcon> <template v-slot:appendIcon>
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<template v-slot:activator> <template v-slot:activator>
<v-list-item-content <v-list-item-content
v-if="item" v-if="item"
@click="clickParentEvent(item)" @click="clickItemEvent(item)"
active-class="active" active-class="active"
> >
<v-list-item-title v-text="item.title" /> <v-list-item-title v-text="item.title" />
...@@ -42,8 +42,8 @@ ...@@ -42,8 +42,8 @@
<tree <tree
:items="item.children" :items="item.children"
:depth="depth+1" :depth="depth+1"
@clickParentEvent="clickParentEvent" :selectedItem="selectedItem"
@clickChildEvent="clickChildEvent" @clickItemEvent="clickItemEvent"
/> />
</v-list-group> </v-list-group>
</div> </div>
...@@ -58,6 +58,9 @@ export default { ...@@ -58,6 +58,9 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
selectedItem: {
type: Object,
},
depth: { depth: {
type: [String, Number], type: [String, Number],
default: 1 default: 1
...@@ -67,16 +70,12 @@ export default { ...@@ -67,16 +70,12 @@ export default {
active: [], active: [],
}), }),
methods: { methods: {
clickParentEvent(item){ clickItemEvent(item){
this.$emit('clickParentEvent', item) this.$emit('clickItemEvent', item)
},
clickChildEvent(item){
this.$emit('clickChildEvent', item)
},
isActiveMenu(id) {
return this.$route.path.indexOf(id) > -1
}, },
}, },
watch: {
},
mounted() { mounted() {
}, },
} }
......
...@@ -65,8 +65,8 @@ ...@@ -65,8 +65,8 @@
<v-list v-if="guideIndex && !loadingPageList"> <v-list v-if="guideIndex && !loadingPageList">
<tree <tree
:items="guideIndex" :items="guideIndex"
@clickParentEvent="openGuide" :selectedItem="selectedIndex"
@clickChildEvent="openGuide" @clickItemEvent="openGuide"
/> />
</v-list> </v-list>
...@@ -98,11 +98,28 @@ ...@@ -98,11 +98,28 @@
<img @click="$router.push('/')" src="/logo_v2/01.png" cover height="28"/> <img @click="$router.push('/')" src="/logo_v2/01.png" cover height="28"/>
</span> </span>
<v-btn
v-if="$store.state.editMode"
@click="editDialogActive = true"
elevation="0"
color="primary"
class="ml-4"
>인덱스 관리</v-btn>
<v-btn
v-if="$store.state.editMode"
@click="$store.state.editMode = false"
elevation="0"
color="primary"
class="ml-4"
>편집모드 해제</v-btn>
<v-spacer/> <v-spacer/>
<div class="search-container" v-show="showSearch"> <div class="search-container" v-show="showSearch">
<v-text-field <v-text-field
v-model="keyword" v-model="keyword"
@keydown.enter="editOn"
:maxlength="50" :maxlength="50"
solo solo
dense dense
...@@ -138,50 +155,108 @@ ...@@ -138,50 +155,108 @@
</v-row> </v-row>
<v-progress-linear absolute bottom :value="scrollPositionRate" height="1" background-color="#eee" color="primary" style="transition:none !important;"></v-progress-linear> <v-progress-linear absolute bottom :value="scrollPositionRate" height="1" background-color="#eee" color="primary" style="transition:none !important;"></v-progress-linear>
<v-dialog
v-model="editDialogActive"
>
<div class="edit-dialog-container">
<h2 class="pa-4 text-center">인덱스 관리</h2>
<v-icon @click="formData = null, editFormMethod = '', editDialogActive = false" class="close-btn">mdi-close</v-icon>
<v-row class="pa-10 pt-0 justify-space-around" no-gutters style="width: 100%; height: calc(100% - 68px);">
<v-col cols="5" style="height: 100%;">
<v-subheader>인덱스 목록</v-subheader>
<v-list v-if="editModeList && !loadingPageList">
<tree
:items="editModeList"
:selectedItem="formData"
@clickItemEvent="setEditTarget"
/>
</v-list>
</v-col>
<v-col cols="5" style="height: 100%;">
<div style="height: 35%;">
<v-subheader>인덱스 이상치</v-subheader>
<v-list>
<v-list-item
v-for="(item, index) in editModeRemainList"
:key="index"
@click="setEditTarget(item)"
>
<v-list-item-title>{{item.title}}</v-list-item-title>
</v-list-item>
</v-list>
</div>
<!-- form -->
<div v-if="formData" style="height: 60%;">
<v-subheader>
{{editFormMethod}}
<v-spacer></v-spacer>
<v-icon size="16" @click="formData = null, editFormMethod = ''">mdi-close</v-icon>
</v-subheader>
<v-form>
<v-container class="pt-6 text-center">
<v-select
:items="['ko','en']"
v-model="formData.locale"
label="Locale"
dense
/>
<v-text-field
v-model="formData.title"
label="Title"
type="text"
/>
<v-text-field
v-model="formData.order"
label="Order"
type="number"
/>
<v-text-field
v-model="formData.path"
label="Path (ex: 'propect/dashboard/')"
@input="setDepthValue"
type="text"
/>
<v-text-field
v-model="formData.depth"
:disabled="editFormMethod !== '수정'"
label="Depth"
type="number"
/>
<v-btn
@click="editFormMethod === '추가' ? createIndex() : updateIndex()"
elevation="0"
color="primary"
>저장</v-btn>
<v-btn
@click="removeIndex"
elevation="0"
color="primary"
:disabled="editFormMethod !== '수정'"
>삭제</v-btn>
</v-container>
</v-form>
</div>
<v-btn
v-else
@click="editFormMethod = '추가', formData={locale: 'ko', order: 0, depth: 1}"
elevation="0"
color="primary"
>인덱스 추가</v-btn>
</v-col>
</v-row>
</div>
</v-dialog>
</v-app-bar> </v-app-bar>
<v-main> <v-main>
<client-only> <client-only>
<Nuxt/> <Nuxt/>
<!-- <vridge-dialog
ref="newPageDialog"
>
<template
v-slot:content
>
<v-text-field v-model="createGuideTitle" label="타이틀" :maxlength="50"></v-text-field>
<v-tooltip
bottom>
<template v-slot:activator="{on,attrs}">
<v-btn
v-bind="attrs"
v-on="on"
elevation="0"
icon
>
<v-icon small>mdi-comment-question</v-icon>
</v-btn>
</template>
<span>
예시)
https://api.github.com/repos/vazilcompany/vridge-docs/contents/guide/ko/overview/overview.md 일경우
>> overview/overview.md 입력
</span>
</v-tooltip>
<v-text-field v-model="createGuideContentKey" label="가이드 github 경로"></v-text-field>
</template>
<template v-slot:actionButton>
<v-btn @click="createGuide()">등록</v-btn>
</template>
</vridge-dialog> -->
</client-only> </client-only>
</v-main> </v-main>
...@@ -207,6 +282,7 @@ export default{ ...@@ -207,6 +282,7 @@ export default{
clipped: false, clipped: false,
rawIndexList:[], rawIndexList:[],
guideIndex:[], guideIndex:[],
selectedIndex:null,
loadingPageList: false, loadingPageList: false,
targetParentId: null, targetParentId: null,
createGuideTitle : '', createGuideTitle : '',
...@@ -215,6 +291,13 @@ export default{ ...@@ -215,6 +291,13 @@ export default{
call:0, call:0,
scrollPositionRate:0, scrollPositionRate:0,
showSearch:false, showSearch:false,
// 인덱스 관리
editDialogActive: false,
editModeList: [],
editModeRemainList: [],
editFormMethod: '',
formData: null,
}), }),
computed: { computed: {
searchedList() { searchedList() {
...@@ -244,6 +327,7 @@ export default{ ...@@ -244,6 +327,7 @@ export default{
// 가이드 페이지 이동 // 가이드 페이지 이동
openGuide(guide) { openGuide(guide) {
this.selectedIndex = guide
this.$router.push('/' + guide.id) this.$router.push('/' + guide.id)
}, },
...@@ -252,10 +336,12 @@ export default{ ...@@ -252,10 +336,12 @@ export default{
await this.$axios.get('http://localhost:5000/test/index') await this.$axios.get('http://localhost:5000/test/index')
.then(res => { .then(res => {
this.rawIndexList = res.data this.rawIndexList = JSON.parse(JSON.stringify(res.data));
let unclassifiedList = JSON.parse(JSON.stringify(res.data));
this.guideIndex = []
// 최상위 목차(depth==1) 추출(path에 슬래시 갯수가 1개) // 최상위 목차(depth==1) 추출(path에 슬래시 갯수가 1개)
this.rawIndexList.forEach(e => { unclassifiedList.forEach(e => {
if(e.path.split('/').length === 2) if(e.path.split('/').length === 2)
this.guideIndex.push(e) this.guideIndex.push(e)
}); });
...@@ -264,7 +350,7 @@ export default{ ...@@ -264,7 +350,7 @@ export default{
parentEl.children = [] parentEl.children = []
// depth==2 추출 // depth==2 추출
this.rawIndexList.forEach(rawEl => { unclassifiedList.forEach(rawEl => {
if(rawEl.path !== parentEl.path && rawEl.path.includes(parentEl.path)){ if(rawEl.path !== parentEl.path && rawEl.path.includes(parentEl.path)){
parentEl.children.push(rawEl) parentEl.children.push(rawEl)
} }
...@@ -284,6 +370,7 @@ export default{ ...@@ -284,6 +370,7 @@ export default{
this.activeNav() this.activeNav()
this.loadingPageList = false this.loadingPageList = false
this.createEditModeList()
// this.activeGuideTreeIndex(this.guideIndex) // this.activeGuideTreeIndex(this.guideIndex)
}, },
...@@ -358,50 +445,112 @@ export default{ ...@@ -358,50 +445,112 @@ export default{
}, },
// 가이드 생성 editOn(){
createGuide() { if(this.keyword === '열려라 문'){
if(!this.createGuideTitle || !this.createGuideContentKey) { this.editDialogActive = true
alert('내용 누락') this.$store.state.editMode = true
return
} }
let guide = { },
parentId:this.targetParentId, createEditModeList(){
title: this.createGuideTitle, let unclassifiedList = JSON.parse(JSON.stringify(this.rawIndexList));
contentKey: this.createGuideContentKey this.editModeList = []
this.editModeRemainList = []
// depth 이상치 확인
unclassifiedList.forEach(index => {
if(!index.depth || index.depth < 1){
this.editModeRemainList.push(index)
delete unclassifiedList[unclassifiedList.indexOf(index)]
}
})
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
// depth 1 리스트 생성
unclassifiedList.forEach(index => {
if(index.depth === 1){
this.editModeList.push(index)
delete unclassifiedList[unclassifiedList.indexOf(index)] // empty 처리
}
})
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
// depth 2 이상인 인덱스들 트리구조 생성
this.setEditModeChildren(this.editModeList, unclassifiedList)
unclassifiedList = unclassifiedList.filter(el => el) // empty 값 삭제
this.editModeRemainList.push(...unclassifiedList)
},
setEditModeChildren(targetList, unclassifiedList){
targetList.forEach(el => {
el.children = []
unclassifiedList.forEach(ucsfEl => {
if(ucsfEl.path.indexOf(el.path) > -1 && ucsfEl.depth === el.depth + 1){
el.children.push(ucsfEl)
delete unclassifiedList[unclassifiedList.indexOf(ucsfEl)]
}
})
if(el.children.length > 0){
this.setEditModeChildren(el.children, unclassifiedList)
}
})
},
setEditTarget(item){
this.formData = item
this.editFormMethod = '수정'
},
setDepthValue(){
this.formData.depth = this.formData.path.split('/').length - 1
},
createIndex() {
const keys = ['locale', 'title' ,'order', 'path', 'depth']
if(keys.some(key => this.formData[key] === (undefined || null))){
alert('값을 모두 입력해주세요.')
return
} }
this.$axios.post('/guide/index', guide) this.$axios.post('http://localhost:5000/test', this.formData)
.then(res=>{ .then(res=>{
this.$refs.newPageDialog.dialog = false
this.getGuideIndex() this.getGuideIndex()
}) })
.catch(err=>{ .catch(err=>{
console.log(err) console.log(err)
}) })
}, },
updateIndex() {
const keys = ['locale', 'title' ,'order', 'path', 'depth']
if(keys.some(key => this.formData[key] === (undefined || null))){
alert('값을 모두 입력해주세요.')
return
}
openCreateDialog(parentId, prefix) { this.$axios.put('http://localhost:5000/test/index', this.formData)
this.$refs.newPageDialog.dialog = true .then(res=>{
this.createGuideTitle = '' this.getGuideIndex()
this.createGuideContentKey = prefix ? prefix + '/' : '' })
this.targetParentId = parentId .catch(err=>{
console.log(err)
})
}, },
removeIndex(){
removeGuide(guideId){ this.$axios.delete("http://localhost:5000/test", {
this.$axios.delete("/guide", {
params:{ params:{
guideId: guideId, guideId: this.formData.id,
cascade:true
} }
}) })
.then(res => { .then(res => {
this.formData = null
this.editFormMethod = ''
this.getGuideIndex() this.getGuideIndex()
}) })
.catch(err => { .catch(err => {
console.log(err) console.log(err)
}) })
}, },
//가이드 네비게이션 이동 //가이드 네비게이션 이동
goGuideNav(position) { goGuideNav(position) {
window.scrollTo({top:position, behavior:'smooth'}) window.scrollTo({top:position, behavior:'smooth'})
...@@ -488,12 +637,14 @@ export default{ ...@@ -488,12 +637,14 @@ export default{
for(let i=0; i<activeTargetPaths.length; ++i){ for(let i=0; i<activeTargetPaths.length; ++i){
for(let j=0; j<guideList.length; ++j){ for(let j=0; j<guideList.length; ++j){
if(guideList[j].path === activeTargetPaths[i]){ if(guideList[j].path === activeTargetPaths[i]){
this.selectedIndex = guideList[j]
guideList[j].active = true guideList[j].active = true
guideList = guideList[j].children guideList = guideList[j].children
break
} }
} }
} }
} },
}, },
mounted(){ mounted(){
this.getGuideIndex() this.getGuideIndex()
...@@ -528,6 +679,62 @@ export default{ ...@@ -528,6 +679,62 @@ export default{
</script> </script>
<style lang="scss"> <style lang="scss">
.v-dialog {
width: 70vw !important;
height: 80vh;
background-color: white;
.edit-dialog-container{
height: 100%;
position: relative;
.close-btn {
font-size: 26px;
position: absolute;
top: 20px;
right: 20px;
}
.col {
.v-subheader {
font-size: 1rem;
}
.v-list {
height: calc(100% - 48px);
background-color: rgba(0, 0, 0, 0.034);
border-radius: 10px;
overflow: auto;
.v-list-item {
&.active, &.v-list-item--active{
*{
font-weight: bold !important;
color: #1E88E5;
}
background-color: rgba(128,128,128,0.07) !important;
}
}
}
&:nth-child(2) {
display: flex;
flex-direction: column;
justify-content: space-between;
.v-list {
height: calc(100% - 48px);
}
}
.v-form {
height: calc(100% - 48px);
background-color: rgba(0, 0, 0, 0.034);
border-radius: 10px;
overflow: auto;
}
}
}
}
html.search-on { html.search-on {
@media screen and (max-width: 600px) { @media screen and (max-width: 600px) {
overflow: hidden !important; overflow: hidden !important;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment