
--- client/resources/css/common/common.css
+++ client/resources/css/common/common.css
... | ... | @@ -19,7 +19,8 @@ |
19 | 19 |
background-color: #f6f6f6 !important; |
20 | 20 |
color: #999; |
21 | 21 |
} |
22 |
-select:focus {border: 0;} |
|
22 |
+select{background-color: transparent ;} |
|
23 |
+select:focus {border: 0; outline: 0;} |
|
23 | 24 |
textarea{resize: none;} |
24 | 25 |
|
25 | 26 |
|
... | ... | @@ -59,7 +60,8 @@ |
59 | 60 |
|
60 | 61 |
|
61 | 62 |
.btn-group button{ |
62 |
- padding: 15px 30px; |
|
63 |
+ width: 130px; |
|
64 |
+ padding: 15px 0px; |
|
63 | 65 |
border-radius: 10px; |
64 | 66 |
font-size: 20px; |
65 | 67 |
|
... | ... | @@ -76,7 +78,7 @@ |
76 | 78 |
} |
77 | 79 |
} |
78 | 80 |
|
79 |
-.button{ |
|
81 |
+.button, button{ |
|
80 | 82 |
&.red-line{ |
81 | 83 |
border: 1px solid #ce3e48; |
82 | 84 |
color: #ce3e48; |
... | ... | @@ -118,4 +120,20 @@ |
118 | 120 |
color: #a5067b; |
119 | 121 |
} |
120 | 122 |
} |
123 |
+ &.gradient{ |
|
124 |
+ position: relative; |
|
125 |
+ background: linear-gradient(132deg, #3e355c, #763954); |
|
126 |
+ color: #fff; |
|
127 |
+ padding-right: 20px; |
|
128 |
+ &::after{ |
|
129 |
+ content: ''; |
|
130 |
+ background-image: url(../../images/icon/down.png); |
|
131 |
+ width: 15px; |
|
132 |
+ height: 15px; |
|
133 |
+ display: block; |
|
134 |
+ position: absolute; |
|
135 |
+ right: 17px; |
|
136 |
+ top: 20px; |
|
137 |
+ } |
|
138 |
+ } |
|
121 | 139 |
}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/css/user/layout.css
+++ client/resources/css/user/layout.css
... | ... | @@ -1,4 +1,13 @@ |
1 |
+header{ |
|
2 |
+ width: 100%; |
|
3 |
+ position: fixed; |
|
4 |
+ top: 0; |
|
5 |
+ z-index: 20; |
|
6 |
+ background-color: #fff; |
|
7 |
+} |
|
1 | 8 |
.container{ |
9 |
+ margin: 120px 0; |
|
10 |
+ position: relative; |
|
2 | 11 |
} |
3 | 12 |
.header-container{ |
4 | 13 |
width: 1500px; |
... | ... | @@ -11,8 +20,49 @@ |
11 | 20 |
.nav-wrap{display: flex;} |
12 | 21 |
|
13 | 22 |
nav{ |
14 |
- ul{display: flex; gap: 102px;} |
|
15 |
- li{font-size: 22px;} |
|
23 |
+ & > ul{display: flex; gap: 102px;} |
|
24 |
+ & > ul > li{font-size: 22px; position: relative;} |
|
25 |
+ |
|
26 |
+ & > ul > li:hover .submenu { |
|
27 |
+ opacity: 1; /* hover시 보이도록 */ |
|
28 |
+ visibility: visible; /* hover시 보이도록 */ |
|
29 |
+ } |
|
30 |
+ .submenu{ |
|
31 |
+ position: absolute; |
|
32 |
+ top: 51px; |
|
33 |
+ left: 50%; |
|
34 |
+ transform: translateX(-50%); |
|
35 |
+ z-index: 2; |
|
36 |
+ width: 180px; |
|
37 |
+ padding: 30px 20px; |
|
38 |
+ border-radius: 20px; |
|
39 |
+ box-shadow: 1px 0px 20px 0px rgba(0, 0, 0, 0.15); |
|
40 |
+ background-color: #fff; |
|
41 |
+ opacity: 0; /* 기본적으로 숨김 */ |
|
42 |
+ visibility: hidden; /* 기본적으로 숨김 */ |
|
43 |
+ transition: opacity 0.3s ease, visibility 0.3s ease; |
|
44 |
+ p{ |
|
45 |
+ padding-left: 10px; |
|
46 |
+ font-size: 18px; |
|
47 |
+ a{display: contents;} |
|
48 |
+ } |
|
49 |
+ p:hover{ |
|
50 |
+ background-color: #f9ebed; |
|
51 |
+ } |
|
52 |
+ } |
|
53 |
+ .submenu::before{ |
|
54 |
+ content: ""; |
|
55 |
+ display: block; |
|
56 |
+ width: 0; |
|
57 |
+ height: 0; |
|
58 |
+ border-left: 17px solid transparent; |
|
59 |
+ border-right: 17px solid transparent; |
|
60 |
+ border-bottom: 17px solid #fff; |
|
61 |
+ position: absolute; |
|
62 |
+ top: -15px; |
|
63 |
+ left: 50%; |
|
64 |
+ transform: translateX(-50%); |
|
65 |
+ } |
|
16 | 66 |
} |
17 | 67 |
.auth-area{ |
18 | 68 |
display: flex; align-items: center; gap: 50px; |
... | ... | @@ -44,4 +94,5 @@ |
44 | 94 |
|
45 | 95 |
} |
46 | 96 |
|
47 |
-.scroll-up{position: fixed; right: 0; bottom: 0;}(파일 끝에 줄바꿈 문자 없음) |
|
97 |
+.scroll-up{position: fixed; right: 5%; |
|
98 |
+ bottom: 101px;}(파일 끝에 줄바꿈 문자 없음) |
--- client/resources/css/user/sub.css
+++ client/resources/css/user/sub.css
... | ... | @@ -1,3 +1,9 @@ |
1 |
+.hr { |
|
2 |
+ background-color: #eeeeee; |
|
3 |
+ margin: 10px 0; |
|
4 |
+ width: 100%; |
|
5 |
+ height: 1px; |
|
6 |
+} |
|
1 | 7 |
.content { |
2 | 8 |
width: 1500px; |
3 | 9 |
margin: 0 auto; |
... | ... | @@ -19,7 +25,7 @@ |
19 | 25 |
content: ''; |
20 | 26 |
background: url(../../images/icon/subtitle.png); |
21 | 27 |
width: 7px; |
22 |
- top: 11px; |
|
28 |
+ top: 6px; |
|
23 | 29 |
left: 0; |
24 | 30 |
height: 17px; |
25 | 31 |
display: block; |
... | ... | @@ -102,6 +108,7 @@ |
102 | 108 |
font-size: 16px; |
103 | 109 |
color: #000; |
104 | 110 |
font-family: "Pretendard-L"; |
111 |
+ margin-left: 5px; |
|
105 | 112 |
} |
106 | 113 |
|
107 | 114 |
&:first-child { |
... | ... | @@ -136,7 +143,7 @@ |
136 | 143 |
} |
137 | 144 |
} |
138 | 145 |
|
139 |
-form { |
|
146 |
+form, .form { |
|
140 | 147 |
margin: 0 auto; |
141 | 148 |
border: 1px solid #dddddd; |
142 | 149 |
border-radius: 20px; |
... | ... | @@ -182,12 +189,7 @@ |
182 | 189 |
} |
183 | 190 |
} |
184 | 191 |
|
185 |
- .hr { |
|
186 |
- background-color: #eeeeee; |
|
187 |
- margin: 10px 0; |
|
188 |
- width: 100%; |
|
189 |
- height: 1px; |
|
190 |
- } |
|
192 |
+ |
|
191 | 193 |
} |
192 | 194 |
|
193 | 195 |
/* 카테고리 */ |
... | ... | @@ -386,6 +388,7 @@ |
386 | 388 |
|
387 | 389 |
|
388 | 390 |
} |
391 |
+ |
|
389 | 392 |
.select-box { |
390 | 393 |
width: 106px; |
391 | 394 |
height: 30px; |
... | ... | @@ -397,13 +400,16 @@ |
397 | 400 |
margin-left: 30px; |
398 | 401 |
|
399 | 402 |
select { |
400 |
- width: 106px; |
|
403 |
+ |
|
404 |
+ width: 100%; |
|
401 | 405 |
border: 0; |
402 | 406 |
font-size: 14px; |
403 | 407 |
} |
404 | 408 |
} |
409 |
+ |
|
405 | 410 |
.search-result { |
406 |
- |
|
411 |
+ margin-top: 40px; |
|
412 |
+ |
|
407 | 413 |
|
408 | 414 |
|
409 | 415 |
.resultext { |
... | ... | @@ -437,7 +443,7 @@ |
437 | 443 |
border: 1px solid #ddd; |
438 | 444 |
border-radius: 24px; |
439 | 445 |
padding: 30px; |
440 |
- |
|
446 |
+ margin-bottom: 30px; |
|
441 | 447 |
|
442 | 448 |
} |
443 | 449 |
|
... | ... | @@ -703,6 +709,44 @@ |
703 | 709 |
font-size: 30px; |
704 | 710 |
} |
705 | 711 |
|
712 |
+ /* pagination */ |
|
713 |
+ .pagination { |
|
714 |
+ button { |
|
715 |
+ width: 40px; |
|
716 |
+ height: 40px; |
|
717 |
+ margin: 0 4px; |
|
718 |
+ border: 1px solid #f0f1f4; |
|
719 |
+ background-color: #fff; |
|
720 |
+ border-radius: 50px; |
|
721 |
+ } |
|
722 |
+ |
|
723 |
+ button.page-number { |
|
724 |
+ font-size: 20px; |
|
725 |
+ color: #555555; |
|
726 |
+ width: 40px; |
|
727 |
+ height: 40px; |
|
728 |
+ background-color: #f0f1f4; |
|
729 |
+ border-radius: 50px; |
|
730 |
+ } |
|
731 |
+ |
|
732 |
+ button.page-number.clicked { |
|
733 |
+ background-color: #ce3e48; |
|
734 |
+ color: #fff; |
|
735 |
+ } |
|
736 |
+ |
|
737 |
+ .anticon { |
|
738 |
+ svg { |
|
739 |
+ font-size: 17px; |
|
740 |
+ |
|
741 |
+ path { |
|
742 |
+ color: #636364; |
|
743 |
+ } |
|
744 |
+ } |
|
745 |
+ |
|
746 |
+ |
|
747 |
+ } |
|
748 |
+ } |
|
749 |
+ |
|
706 | 750 |
.modal-search { |
707 | 751 |
padding: 13px; |
708 | 752 |
background-color: rgba(0, 61, 97, 0.05); |
... | ... | @@ -811,6 +855,15 @@ |
811 | 855 |
margin-bottom: 107px; |
812 | 856 |
position: relative; |
813 | 857 |
|
858 |
+ &.video { |
|
859 |
+ margin-bottom: 50px; |
|
860 |
+ |
|
861 |
+ img { |
|
862 |
+ height: 800px; |
|
863 |
+ border-radius: 20px; |
|
864 |
+ } |
|
865 |
+ } |
|
866 |
+ |
|
814 | 867 |
.main-swiper { |
815 | 868 |
.swiper { |
816 | 869 |
width: 100%; |
... | ... | @@ -872,23 +925,45 @@ |
872 | 925 |
|
873 | 926 |
} |
874 | 927 |
|
928 |
+ .img-box { |
|
929 |
+ margin-right: 50px; |
|
930 |
+ width: 600px; |
|
931 |
+ height: 360px; |
|
932 |
+ } |
|
933 |
+ |
|
875 | 934 |
.info-form { |
876 | 935 |
background-image: linear-gradient(#fff, #fff), linear-gradient(-45deg, #ca3e49, #3f355c); |
877 | 936 |
background-origin: border-box; |
878 | 937 |
background-clip: content-box, border-box; |
879 | 938 |
border: 3px solid transparent; |
880 | 939 |
border-radius: 32px; |
940 |
+ padding: 0; |
|
941 |
+ height: 360px; |
|
881 | 942 |
|
882 |
- dd { |
|
883 |
- background-color: transparent; |
|
943 |
+ .info-box { |
|
944 |
+ padding: 50px 40px; |
|
884 | 945 |
|
885 |
- p { |
|
886 |
- font-size: 20px; |
|
887 |
- text-align: left; |
|
888 |
- font-family: "Pretendard-L"; |
|
946 |
+ dl { |
|
947 |
+ border: 0; |
|
948 |
+ padding: 37px; |
|
949 |
+ border-radius: 20px; |
|
950 |
+ background-color: #f5f6f8; |
|
951 |
+ |
|
952 |
+ dd { |
|
953 |
+ background-color: transparent; |
|
954 |
+ padding: 0; |
|
955 |
+ |
|
956 |
+ p { |
|
957 |
+ font-size: 20px; |
|
958 |
+ text-align: left; |
|
959 |
+ font-family: "Pretendard-L"; |
|
960 |
+ } |
|
961 |
+ } |
|
889 | 962 |
} |
890 | 963 |
} |
964 |
+ |
|
891 | 965 |
} |
966 |
+ |
|
892 | 967 |
|
893 | 968 |
.btn-group { |
894 | 969 |
margin-top: 24px; |
... | ... | @@ -918,97 +993,142 @@ |
918 | 993 |
} |
919 | 994 |
|
920 | 995 |
/* 회원관리, 카테고리 관리 */ |
921 |
-.management{ |
|
922 |
- .left-con{width: 350px; margin-right: 55px;} |
|
996 |
+.management { |
|
997 |
+ .left-con { |
|
998 |
+ width: 350px; |
|
999 |
+ margin-right: 55px; |
|
1000 |
+ } |
|
923 | 1001 |
|
924 |
- .search-area{ |
|
1002 |
+ .search-area { |
|
925 | 1003 |
gap: 5px; |
926 | 1004 |
padding: 0; |
927 | 1005 |
height: 40px; |
928 | 1006 |
} |
929 |
- .search-wrap{ |
|
930 |
- |
|
1007 |
+ |
|
1008 |
+ .search-wrap { |
|
1009 |
+ |
|
931 | 1010 |
position: initial; |
932 | 1011 |
transform: none; |
933 |
- |
|
934 |
- input{ |
|
1012 |
+ |
|
1013 |
+ input { |
|
935 | 1014 |
width: 100%; |
936 | 1015 |
height: 100%; |
937 | 1016 |
border: 1px solid #000; |
938 | 1017 |
border-radius: 5px; |
939 | 1018 |
} |
1019 |
+ |
|
940 | 1020 |
.select-box { |
941 | 1021 |
border-color: #000; |
942 | 1022 |
width: 100px; |
943 | 1023 |
height: 100%; |
944 |
- select{width: 100%; color: #000; } |
|
1024 |
+ |
|
1025 |
+ select { |
|
1026 |
+ width: 100%; |
|
1027 |
+ color: #000; |
|
1028 |
+ } |
|
945 | 1029 |
} |
946 |
- .search-btn{ |
|
1030 |
+ |
|
1031 |
+ .search-btn { |
|
947 | 1032 |
position: relative; |
948 | 1033 |
flex-shrink: 0; |
949 | 1034 |
width: 40px; |
950 | 1035 |
height: 40px; |
951 | 1036 |
background-color: #000; |
952 | 1037 |
border-radius: 5px; |
953 |
- img{ |
|
1038 |
+ |
|
1039 |
+ img { |
|
954 | 1040 |
position: absolute; |
955 | 1041 |
left: 50%; |
956 | 1042 |
right: 50%; |
957 | 1043 |
transform: translateX(-50%) translateY(-50%); |
958 | 1044 |
} |
959 |
- |
|
1045 |
+ |
|
960 | 1046 |
} |
961 | 1047 |
} |
962 |
- .btn-group-small{ |
|
1048 |
+ |
|
1049 |
+ .btn-group-small { |
|
963 | 1050 |
gap: 5px; |
964 |
- .button{ |
|
965 |
- &.flex{ |
|
966 |
- img{text-align: left; margin-right: 10px;} |
|
967 |
- p{text-align: center;} |
|
1051 |
+ |
|
1052 |
+ .button { |
|
1053 |
+ &.flex { |
|
1054 |
+ img { |
|
1055 |
+ text-align: left; |
|
1056 |
+ margin-right: 10px; |
|
1057 |
+ } |
|
1058 |
+ |
|
1059 |
+ p { |
|
1060 |
+ text-align: center; |
|
1061 |
+ } |
|
968 | 1062 |
} |
969 | 1063 |
} |
970 | 1064 |
} |
1065 |
+ |
|
971 | 1066 |
.select-box { |
972 | 1067 |
width: 230px; |
973 | 1068 |
height: 50px; |
974 | 1069 |
border: 1px solid #ddd; |
975 | 1070 |
padding: 0 15px; |
976 | 1071 |
margin: 0; |
1072 |
+ |
|
977 | 1073 |
select { |
978 | 1074 |
width: 100%; |
979 | 1075 |
height: 100%; |
980 | 1076 |
font-size: 16px; |
981 |
- color: #999999; |
|
1077 |
+ color: #999999; |
|
982 | 1078 |
} |
983 | 1079 |
} |
984 |
- .switch{ |
|
985 |
- label{margin-top: -12px;} |
|
1080 |
+ |
|
1081 |
+ .switch { |
|
1082 |
+ label { |
|
1083 |
+ margin-top: -12px; |
|
1084 |
+ } |
|
986 | 1085 |
} |
987 |
- table{ |
|
1086 |
+ |
|
1087 |
+ table { |
|
988 | 1088 |
border-radius: 10px; |
989 | 1089 |
overflow: hidden; |
990 |
- |
|
991 |
- thead{ |
|
1090 |
+ |
|
1091 |
+ thead { |
|
992 | 1092 |
background-color: #636364; |
993 |
- th{color: #fff; border: 1px solid #eee;} |
|
1093 |
+ |
|
1094 |
+ th { |
|
1095 |
+ color: #fff; |
|
1096 |
+ border: 1px solid #eee; |
|
1097 |
+ } |
|
994 | 1098 |
} |
995 |
- td{border: 1px solid #eee; text-align: center;} |
|
996 |
- tr:hover td{ |
|
1099 |
+ |
|
1100 |
+ td { |
|
1101 |
+ border: 1px solid #eee; |
|
1102 |
+ text-align: center; |
|
1103 |
+ } |
|
1104 |
+ |
|
1105 |
+ tbody tr:hover td { |
|
997 | 1106 |
background-color: #007ac3; |
998 |
- overflow: hidden; |
|
999 |
- width: 100%; |
|
1000 |
- border: 2px solid transparent; /* Set a transparent border first */ |
|
1001 |
- border-image: linear-gradient(-45deg, #ce3e48, #3f355c) 1; |
|
1107 |
+ /* Change background color of the cell */ |
|
1108 |
+ border: 2px solid transparent; |
|
1109 |
+ /* Set transparent border to apply gradient */ |
|
1110 |
+ border-image: linear-gradient(-45deg, #ce3e48, #3f355c) 1; |
|
1111 |
+ /* Apply gradient border */ |
|
1112 |
+ } |
|
1113 |
+ |
|
1114 |
+ /* Optionally, add hover effect on the whole row */ |
|
1115 |
+ tbody tr:hover { |
|
1116 |
+ cursor: pointer; |
|
1002 | 1117 |
} |
1003 | 1118 |
} |
1119 |
+ |
|
1004 | 1120 |
.delete-member { |
1005 |
- background-color: #f5f8f9; |
|
1006 |
- td{ |
|
1121 |
+ background-color: #f5f8f9; |
|
1122 |
+ |
|
1123 |
+ td { |
|
1007 | 1124 |
color: #aaaaaa; |
1008 |
- |
|
1125 |
+ |
|
1009 | 1126 |
} |
1010 |
- } |
|
1011 |
- textarea{border-color: #ddd;} |
|
1127 |
+ } |
|
1128 |
+ |
|
1129 |
+ textarea { |
|
1130 |
+ border-color: #ddd; |
|
1131 |
+ } |
|
1012 | 1132 |
} |
1013 | 1133 |
|
1014 | 1134 |
|
... | ... | @@ -1133,7 +1253,8 @@ |
1133 | 1253 |
display: block; |
1134 | 1254 |
border-radius: 100px; |
1135 | 1255 |
position: relative; |
1136 |
- transition: background-color 0.3s ease; /* Added smooth transition for background */ |
|
1256 |
+ transition: background-color 0.3s ease; |
|
1257 |
+ /* Added smooth transition for background */ |
|
1137 | 1258 |
} |
1138 | 1259 |
|
1139 | 1260 |
label:after { |
... | ... | @@ -1145,22 +1266,26 @@ |
1145 | 1266 |
height: 20px; |
1146 | 1267 |
background: #fff; |
1147 | 1268 |
border-radius: 50%; |
1148 |
- transition: left 0.3s ease, transform 0.3s ease; /* Smooth transition for the toggle ball */ |
|
1269 |
+ transition: left 0.3s ease, transform 0.3s ease; |
|
1270 |
+ /* Smooth transition for the toggle ball */ |
|
1149 | 1271 |
} |
1150 | 1272 |
|
1151 | 1273 |
/* When the input is checked */ |
1152 |
- input:checked + label { |
|
1153 |
- background: #787878; /* Color when active */ |
|
1274 |
+ input:checked+label { |
|
1275 |
+ background: #787878; |
|
1276 |
+ /* Color when active */ |
|
1154 | 1277 |
} |
1155 | 1278 |
|
1156 |
- input:checked + label:after { |
|
1279 |
+ input:checked+label:after { |
|
1157 | 1280 |
left: calc(100% - 5px); |
1158 |
- transform: translateX(-100%); /* Keeps the ball moving smoothly */ |
|
1281 |
+ transform: translateX(-100%); |
|
1282 |
+ /* Keeps the ball moving smoothly */ |
|
1159 | 1283 |
} |
1160 | 1284 |
|
1161 | 1285 |
/* Optional: Make the toggle ball slightly larger during the click for a better effect */ |
1162 | 1286 |
label:active:after { |
1163 |
- width: 20px; /* Slightly increase the ball size */ |
|
1287 |
+ width: 20px; |
|
1288 |
+ /* Slightly increase the ball size */ |
|
1164 | 1289 |
height: 20px; |
1165 | 1290 |
} |
1166 | 1291 |
} |
+++ client/resources/images/icon/down.png
Binary file is not shown |
+++ client/resources/images/img8.png
Binary file is not shown |
+++ client/resources/images/list_icon01_off.png
Binary file is not shown |
+++ client/resources/images/list_icon01_on.png
Binary file is not shown |
+++ client/resources/images/list_icon02_off.png
Binary file is not shown |
+++ client/resources/images/list_icon02_on.png
Binary file is not shown |
--- client/resources/images/visual.png
+++ client/resources/images/visual.png
Binary file is not shown |
--- client/views/layout/Header.vue
+++ client/views/layout/Header.vue
... | ... | @@ -1,37 +1,57 @@ |
1 | 1 |
<template> |
2 |
- <header> |
|
3 |
- <div class="header-container w1500"> |
|
4 |
- <div class="logo-wrap"> |
|
5 |
- <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link> |
|
6 |
- </div> |
|
7 |
- <div class="nav-wrap"> |
|
8 |
- <nav> |
|
9 |
- <ul> |
|
10 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000001')"><router-link :to="{ path: '/PicHistorySearch.page' }">기록물</router-link></li> |
|
11 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000004')"><router-link :to="{ path: '/' }">언론에서 바라본 구미시</router-link></li> |
|
12 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000007')"><router-link :to="{ path: '/MemberManagement.page' }">회원관리</router-link></li> |
|
13 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" @click="updateMenuStats('MENU_00000008')"><router-link :to="{ path: '/CategoryManagement.page' }">카테고리 관리</router-link></li> |
|
14 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000001')">기록물</li> |
|
15 |
- <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li> |
|
16 |
- </ul> |
|
17 |
- </nav> |
|
18 |
- </div> |
|
19 |
- <div class="auth-area"> |
|
20 |
- <ul v-if="$store.state.userId != null"> |
|
21 |
- <li><img src="../../resources/images/icon/user-settings-line.png" alt=""> |
|
22 |
- <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link> |
|
23 |
- </li> |
|
24 |
- <li> |
|
25 |
- <div class="line"></div> |
|
26 |
- </li> |
|
27 |
- <li><img src="../../resources/images/icon/logout-box-line.png" alt=""> |
|
28 |
- <a href="#" @click.prevent="logout">로그아웃</a> |
|
29 |
- </li> |
|
30 |
- </ul> |
|
31 |
- <a href="#" class="all-menu"><img src="../../resources/images/allmenu.png" alt=""></a> |
|
32 |
- </div> |
|
33 |
- </div> |
|
34 |
- </header> |
|
2 |
+ |
|
3 |
+ <header> |
|
4 |
+ <div class="header-container w1500"> |
|
5 |
+ <div class="logo-wrap"> |
|
6 |
+ <router-link :to="{ path: '/' }" class="logo"><img :src="logo" alt=""></router-link> |
|
7 |
+ </div> |
|
8 |
+ <div class="nav-wrap"> |
|
9 |
+ <nav> |
|
10 |
+ <ul> |
|
11 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
|
12 |
+ @click="updateMenuStats('MENU_00000001')">기록물 |
|
13 |
+ <div class="submenu"> |
|
14 |
+ <p>• <router-link :to="{ path: '/PicHistorySearch.page' }" >사진 기록물</router-link></p> |
|
15 |
+ <div class="hr"></div> |
|
16 |
+ <p>• <router-link :to="{ path: '/VideoHistorySearch.page' }" >영상 기록물</router-link></p> |
|
17 |
+ </div> |
|
18 |
+ </li> |
|
19 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
|
20 |
+ @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시 |
|
21 |
+ <div class="submenu"> |
|
22 |
+ <p>• <router-link :to="{ path: '/MediaVideoSearch.page' }" >미디어 영상</router-link></p> |
|
23 |
+ <div class="hr"></div> |
|
24 |
+ <p>• <router-link :to="{ path: '/NewsReleaseSearch.page' }" >보도자료</router-link></p> |
|
25 |
+ </div> |
|
26 |
+ </li> |
|
27 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
|
28 |
+ @click="updateMenuStats('MENU_00000007')"><router-link :to="{ path: '/MemberManagement.page' }" >회원관리</router-link></li> |
|
29 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_ADMIN'" |
|
30 |
+ @click="updateMenuStats('MENU_00000008')"><router-link :to="{ path: '/CategoryManagement.page' }" >카테고리 관리</router-link></li> |
|
31 |
+ |
|
32 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" |
|
33 |
+ @click="updateMenuStats('MENU_00000001')">기록물</li> |
|
34 |
+ <li v-if="$store.state.roles[0]?.authority === 'ROLE_USER'" |
|
35 |
+ @click="updateMenuStats('MENU_00000004')">언론에서 바라본 구미시</li> |
|
36 |
+ </ul> |
|
37 |
+ </nav> |
|
38 |
+ </div> |
|
39 |
+ <div class="auth-area"> |
|
40 |
+ <ul v-if="$store.state.userId != null"> |
|
41 |
+ <li><img src="../../resources/images/icon/user-settings-line.png" alt=""> |
|
42 |
+ <router-link :to="{ path: '/MyInfo.page' }">{{ $store.state.userNm }}</router-link> |
|
43 |
+ </li> |
|
44 |
+ <li> |
|
45 |
+ <div class="line"></div> |
|
46 |
+ </li> |
|
47 |
+ <li><img src="../../resources/images/icon/logout-box-line.png" alt=""> |
|
48 |
+ <a href="#" @click.prevent="logout">로그아웃</a> |
|
49 |
+ </li> |
|
50 |
+ </ul> |
|
51 |
+ <a href="#" class="all-menu"><img src="../../resources/images/allmenu.png" alt=""></a> |
|
52 |
+ </div> |
|
53 |
+ </div> |
|
54 |
+ </header> |
|
35 | 55 |
</template> |
36 | 56 |
<script> |
37 | 57 |
import { logOutProc } from "../../resources/api/user" |
--- client/views/pages/AppRouter.js
+++ client/views/pages/AppRouter.js
... | ... | @@ -5,16 +5,28 @@ |
5 | 5 |
import Login from "./login/Login.vue"; |
6 | 6 |
import Main from "./main/Main.vue"; |
7 | 7 |
import NotFound from "./etc/NotFound.vue"; |
8 |
+// 회원관리 |
|
9 |
+import MyInfo from "./user/MyInfo.vue"; |
|
8 | 10 |
// 통합검색 |
9 | 11 |
import TotalSearch from "./user/TotalSearch.vue"; |
10 | 12 |
// 사진기록물 |
11 | 13 |
import PicHistorySearch from "./user/PicHistorySearch.vue"; |
12 | 14 |
import PicHistoryInsert from "./user/PicHistoryInsert.vue"; |
13 | 15 |
import PicHistoryDetail from "./user/PicHistoryDetail.vue"; |
16 |
+// 영상기록물 |
|
17 |
+import VideoHistoryInsert from "./user/VideoHistoryInsert.vue"; |
|
18 |
+import VideoHistoryDetail from "./user/VideoHistoryDetail.vue"; |
|
19 |
+import VideoHistorySearch from "./user/VideoHistorySearch.vue"; |
|
20 |
+// 미디어 영상 |
|
21 |
+import MediaVideoInsert from "./user/MediaVideoInsert.vue"; |
|
22 |
+import MediaVideoDetail from "./user/MediaVideoDetail.vue"; |
|
23 |
+import MediaVideoSearch from "./user/MediaVideoSearch.vue"; |
|
14 | 24 |
// 보도자료 |
15 |
-import BodoDetail from "./user/BodoDetail.vue"; |
|
16 |
-// 회원관리 |
|
17 |
-import MyInfo from "./user/MyInfo.vue"; |
|
25 |
+import NewsReleaseDetail from "./user/NewsReleaseDetail.vue"; |
|
26 |
+import NewsReleaseInsert from "./user/NewsReleaseInsert.vue"; |
|
27 |
+import NewsReleaseSearch from "./user/NewsReleaseSearch.vue"; |
|
28 |
+ |
|
29 |
+ |
|
18 | 30 |
import MemberManagement from "./user/MemberManagement.vue"; |
19 | 31 |
// 카테고리관리 |
20 | 32 |
import CategoryManagement from "./user/CategoryManagement.vue"; |
... | ... | @@ -27,10 +39,27 @@ |
27 | 39 |
{ path: "/notFound.page", name: "NotFoundPage", component: NotFound }, |
28 | 40 |
|
29 | 41 |
{ path: "/TotalSearch.page", name: "TotalSearch", component: TotalSearch }, |
42 |
+ // 사진기록물 |
|
30 | 43 |
{ path: "/PicHistorySearch.page", name: "PicHistorySearch", component: PicHistorySearch }, |
31 | 44 |
{ path: "/PicHistoryInsert.page", name: "PicHistoryInsert", component: PicHistoryInsert }, |
32 | 45 |
{ path: "/PicHistoryDetail.page", name: "PicHistoryDetail", component: PicHistoryDetail }, |
33 |
- { path: "/BodoDetail.page", name: "BodoDetail", component: BodoDetail }, |
|
46 |
+ // 영상기록물 |
|
47 |
+ { path: "/VideoHistorySearch.page", name: "VideoHistorySearch", component: VideoHistorySearch }, |
|
48 |
+ { path: "/VideoHistoryInsert.page", name: "VideoHistoryInsert", component: VideoHistoryInsert }, |
|
49 |
+ { path: "/VideoHistoryDetail.page", name: "VideoHistoryDetail", component: VideoHistoryDetail }, |
|
50 |
+ |
|
51 |
+ // 미디어 영상 |
|
52 |
+ { path: "/MediaVideoSearch.page", name: "MediaVideoSearch", component: MediaVideoSearch }, |
|
53 |
+ { path: "/MediaVideoInsert.page", name: "MediaVideoInsert", component: MediaVideoInsert }, |
|
54 |
+ { path: "/MediaVideoDetail.page", name: "MediaVideoDetail", component: MediaVideoDetail }, |
|
55 |
+ |
|
56 |
+// 보도자료 |
|
57 |
+{ path: "/NewsReleaseSearch.page", name: "NewsReleaseSearch", component: NewsReleaseSearch }, |
|
58 |
+ { path: "/NewsReleaseInsert.page", name: "NewsReleaseInsert", component: NewsReleaseInsert }, |
|
59 |
+ { path: "/NewsReleaseDetail.page", name: "NewsReleaseDetail", component: NewsReleaseDetail }, |
|
60 |
+ |
|
61 |
+ |
|
62 |
+ |
|
34 | 63 |
{ path: "/MemberManagement.page", name: "MemberManagement", component: MemberManagement }, |
35 | 64 |
{ path: "/CategoryManagement.page", name: "CategoryManagement", component: CategoryManagement }, |
36 | 65 |
]; |
--- client/views/pages/main/Main.vue
+++ client/views/pages/main/Main.vue
... | ... | @@ -171,7 +171,7 @@ |
171 | 171 |
nomedia: "client/resources/images/no_media.png", |
172 | 172 |
nobodo: "client/resources/images/no_bodo.png", |
173 | 173 |
direct: 'client/resources/images/direct-btn.png', |
174 |
- search: 'client/resources/images/icon/search_btn.png', |
|
174 |
+ search: 'client/resources/images/search_btn.png', |
|
175 | 175 |
selectedTab: "newPhoto", // Set initial tab index to 신규사진기록물 (first tab) |
176 | 176 |
tabs: [ |
177 | 177 |
{ |
--- client/views/pages/user/BodoDetail.vue
+++ client/views/pages/user/MediaVideoDetail.vue
... | ... | @@ -0,0 +1,148 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>미디어 영상</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li><img :src="homeicon" alt="Home Icon"> | |
8 | + <p>언론에서 바라본 구미시</p> | |
9 | + </li> | |
10 | + <li><img :src="righticon" alt=""></li> | |
11 | + <li>미디어 영상</li> | |
12 | + </ul> | |
13 | + </div> | |
14 | + </div> | |
15 | + <form action="" class="gallery-form mb-40"> | |
16 | + <dl class="mb-20"> | |
17 | + <dd> | |
18 | + <p>미디어 영상 제목1 | |
19 | + </p> | |
20 | + <div class="date flex align-center"> | |
21 | + <img :src="calendaricon" alt=""> | |
22 | + <span>2025.02.28</span> | |
23 | + </div> | |
24 | + </dd> | |
25 | + | |
26 | + </dl> | |
27 | + <div class="gallery video"> | |
28 | + <img :src="eximg" alt=""> | |
29 | + </div> | |
30 | + </form> | |
31 | + | |
32 | + <h3>내용</h3> | |
33 | + <form action="" class=" info-form mb-50"> | |
34 | + <dl> | |
35 | + <dd> | |
36 | + <p> 대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 | |
37 | + 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어 있습니다.</p> | |
38 | + | |
39 | + </dd> | |
40 | + </dl> | |
41 | + </form> | |
42 | + | |
43 | + <h3>기본정보</h3> | |
44 | + <form action="" class="info-form mb-50"> | |
45 | + <dl> | |
46 | + <dd class="mb-20"> | |
47 | + <img :src="addressicon" alt=""> | |
48 | + <span>원본주소</span> | |
49 | + <p>https://youtu.be/2F2gWkEnSz4</p> | |
50 | + </dd> | |
51 | + <dd class="mb-20"> | |
52 | + <img :src="yearicon" alt=""> | |
53 | + <span>생산연도</span> | |
54 | + <p>2017</p> | |
55 | + | |
56 | + </dd> | |
57 | + <dd> | |
58 | + <img :src="categoryicon" alt=""> | |
59 | + <span>카테고리</span> | |
60 | + <ul class="category"> | |
61 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
62 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
63 | + </ul> | |
64 | + | |
65 | + </dd> | |
66 | + | |
67 | + </dl> | |
68 | + </form> | |
69 | + <div class="btn-group flex-center"> | |
70 | + <button class="red-line " type="button" @click="fnDeleteUser">삭제</button> | |
71 | + <button class="blue-line " type="button" @click="fnUpdateUser">수정</button> | |
72 | + <button class="gray-line-bg " type="button" @click="fnUpdateUser">목록</button> | |
73 | + </div> | |
74 | + </div> | |
75 | +</template> | |
76 | + | |
77 | +<script> | |
78 | +import axios from "axios"; | |
79 | +import { ref } from 'vue'; | |
80 | +import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user" | |
81 | +// Import Swiper Vue components | |
82 | +import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue'; | |
83 | +import { Swiper, SwiperSlide } from 'swiper/vue'; | |
84 | + | |
85 | +// Import Swiper styles | |
86 | +import 'swiper/css'; | |
87 | + | |
88 | +import 'swiper/css/free-mode'; | |
89 | +import 'swiper/css/navigation'; | |
90 | +import 'swiper/css/thumbs'; | |
91 | + | |
92 | +// import required modules | |
93 | +import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; | |
94 | + | |
95 | +export default { | |
96 | + components: { | |
97 | + PauseOutlined, | |
98 | + CaretRightOutlined, | |
99 | + Swiper, | |
100 | + SwiperSlide, | |
101 | + }, | |
102 | + setup() { | |
103 | + const thumbsSwiper = ref(null); | |
104 | + | |
105 | + const setThumbsSwiper = (swiper) => { | |
106 | + thumbsSwiper.value = swiper; | |
107 | + }; | |
108 | + | |
109 | + return { | |
110 | + thumbsSwiper, | |
111 | + setThumbsSwiper, | |
112 | + modules: [FreeMode, Navigation, Thumbs], | |
113 | + }; | |
114 | + }, | |
115 | + data() { | |
116 | + return { | |
117 | + resultitem: { | |
118 | + category1: true, | |
119 | + category2: true, | |
120 | + }, | |
121 | + calendaricon: 'client/resources/images/icon/calendaricon.png', | |
122 | + homeicon: 'client/resources/images/icon/home.png', | |
123 | + erroricon: 'client/resources/images/icon/error.png', | |
124 | + righticon: 'client/resources/images/icon/right.png', | |
125 | + addressicon: 'client/resources/images/icon/addressicon.png', | |
126 | + yearicon: 'client/resources/images/icon/yearicon.png', | |
127 | + categoryicon: 'client/resources/images/icon/categoryicon.png', | |
128 | + eximg: 'client/resources/images/img8.png', | |
129 | + slides: [ | |
130 | + { img: 'client/resources/images/visual.png', alt: 'Slide 1' }, | |
131 | + { img: 'client/resources/images/visual.png', alt: 'Slide 2' }, | |
132 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
133 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
134 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
135 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
136 | + // Add more slides as needed | |
137 | + ], | |
138 | + | |
139 | + }; | |
140 | + }, | |
141 | + methods: { | |
142 | + }, | |
143 | + watch: {}, | |
144 | + computed: { | |
145 | + }, | |
146 | + mounted() { }, | |
147 | +}; | |
148 | +</script>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/user/MediaVideoInsert.vue
... | ... | @@ -0,0 +1,181 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>미디어 영상</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li><img :src="homeicon" alt="Home Icon"> | |
8 | + <p>언론에서 바라본 구미시</p> | |
9 | + </li> | |
10 | + <li><img :src="righticon" alt=""></li> | |
11 | + <li>미디어 영상</li> | |
12 | + </ul> | |
13 | + </div> | |
14 | + </div> | |
15 | + <form action="" class="insert-form mb-50"> | |
16 | + <dl> | |
17 | + <dd> | |
18 | + <label for="id" class="require">제목</label> | |
19 | + <div class="wfull"><input type="text" id="id" placeholder="제목을 입력하세요."></div> | |
20 | + </dd> | |
21 | + <div class="hr"></div> | |
22 | + <dd> | |
23 | + <label for="year">생산연도</label> | |
24 | + <input type="text" id="year" placeholder="생산연도를 입력하세요"> | |
25 | + </dd> | |
26 | + <div class="hr"></div> | |
27 | + <dd> | |
28 | + <label for="address">주소</label> | |
29 | + <div class="wfull"><input type="text" id="address" placeholder="URL 주소를 입력하세요"></div> | |
30 | + </dd> | |
31 | + <div class="hr"></div> | |
32 | + <dd> | |
33 | + <label for="text">내용</label> | |
34 | + <div class="wfull"> | |
35 | + <EditorComponent :contents="insertDTO.cn" /> | |
36 | + </div> | |
37 | + </dd> | |
38 | + <div class="hr"></div> | |
39 | + <dd> | |
40 | + <label for="category" class="flex align-center"> | |
41 | + <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button> | |
42 | + </label> | |
43 | + <ul class="category"> | |
44 | + <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button> | |
45 | + </li> | |
46 | + </ul> | |
47 | + </dd> | |
48 | + </dl> | |
49 | + </form> | |
50 | + <div class="btn-group flex-center"> | |
51 | + <button class="cancel">취소</button> | |
52 | + <button class="register">등록</button> | |
53 | + </div> | |
54 | + </div> | |
55 | + <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" /> | |
56 | +</template> | |
57 | +<script> | |
58 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
59 | +// COMPONENT | |
60 | +import EditorComponent from '../../component/EditorComponent.vue'; | |
61 | +import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; | |
62 | + | |
63 | +export default { | |
64 | + components: { | |
65 | + DoubleLeftOutlined, | |
66 | + LeftOutlined, | |
67 | + RightOutlined, | |
68 | + DoubleRightOutlined, | |
69 | + EditorComponent, CategorySelectModal, | |
70 | + }, | |
71 | + | |
72 | + data() { | |
73 | + return { | |
74 | + // Define the image sources | |
75 | + homeicon: 'client/resources/images/icon/home.png', | |
76 | + erroricon: 'client/resources/images/icon/error.png', | |
77 | + righticon: 'client/resources/images/icon/right.png', | |
78 | + fileicon: 'client/resources/images/icon/file.png', | |
79 | + searchicon: 'client/resources/images/icon/search.png', | |
80 | + | |
81 | + isModalOpen: false, | |
82 | + | |
83 | + items: [ | |
84 | + { id: 1, category: '카테고리 1', selected: false }, | |
85 | + { id: 2, category: '카테고리 2', selected: false }, | |
86 | + { id: 3, category: '카테고리 3', selected: false }, | |
87 | + ], | |
88 | + fileNames: [], | |
89 | + insertDTO: { | |
90 | + sj: null, //제목 | |
91 | + cn: null, //내용 | |
92 | + adres: null, // 주소 | |
93 | + prdctnYear: null, // 생산연도 | |
94 | + ty: 'P', // 타입 ( P: 사진, V: 영상 ) | |
95 | + multipartFiles: null, // 첨부파일 정보 | |
96 | + ctgryIds: null, // 카테고리 정보 | |
97 | + }, | |
98 | + | |
99 | + files: [], | |
100 | + selectedCtgries: [], // 카테고리 목록 | |
101 | + }; | |
102 | + }, | |
103 | + computed: { | |
104 | + filteredItems() { | |
105 | + // This could be modified to support filtering based on searchQuery | |
106 | + return this.items.filter(item => | |
107 | + item.category.includes(this.searchQuery) | |
108 | + ); | |
109 | + } | |
110 | + }, | |
111 | + created() { | |
112 | + }, | |
113 | + methods: { | |
114 | + registerCategories() { | |
115 | + // Add selected categories to the displayed list | |
116 | + this.selectedCtgries = this.items | |
117 | + .filter(item => item.selected) | |
118 | + .map(item => item.category); | |
119 | + this.closeModal(); // Close modal after registration | |
120 | + }, | |
121 | + removeCategory(index) { | |
122 | + // Remove category from the list | |
123 | + this.selectedCtgries.splice(index, 1); | |
124 | + }, | |
125 | + searchCategories() { | |
126 | + // You can implement search logic if needed | |
127 | + }, | |
128 | + nextPage() { | |
129 | + if (this.currentPage < this.totalPages) { | |
130 | + this.currentPage++; | |
131 | + } | |
132 | + }, | |
133 | + previousPage() { | |
134 | + if (this.currentPage > 1) { | |
135 | + this.currentPage--; | |
136 | + } | |
137 | + }, | |
138 | + showFileNames(event) { | |
139 | + const files = event.target.files; | |
140 | + this.fileNames = []; // Clear previous file names | |
141 | + | |
142 | + for (let i = 0; i < files.length; i++) { | |
143 | + const file = files[i]; | |
144 | + const fileType = file.name.split('.').pop().toLowerCase(); // Get file extension | |
145 | + | |
146 | + // Set default icon | |
147 | + let iconPath = this.fileicons; | |
148 | + | |
149 | + // Determine the icon based on file type | |
150 | + if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) { | |
151 | + iconPath = 'client/resources/images/icon/imgicon.png'; // Example for image files | |
152 | + } else if (['pdf'].includes(fileType)) { | |
153 | + iconPath = 'client/resources/images/icon/pdficon.png'; // Example for PDF files | |
154 | + } else if (['xls'].includes(fileType)) { | |
155 | + iconPath = 'client/resources/images/icon/excelicon.png'; // Example for audio files | |
156 | + } else if (['hwp'].includes(fileType)) { | |
157 | + iconPath = 'client/resources/images/icon/hwpicon.png'; // Example for video files | |
158 | + } | |
159 | + | |
160 | + // Push the file name and corresponding icon to the fileNames array | |
161 | + this.fileNames.push({ | |
162 | + name: file.name, | |
163 | + icon: iconPath | |
164 | + }); | |
165 | + } | |
166 | + }, | |
167 | + removeFile(index) { | |
168 | + // Remove file from the list | |
169 | + this.fileNames.splice(index, 1); | |
170 | + console.log(removeFile) | |
171 | + }, | |
172 | + openModal() { | |
173 | + this.isModalOpen = true; | |
174 | + }, | |
175 | + // 모달 닫기 | |
176 | + closeModal() { | |
177 | + this.isModalOpen = false; | |
178 | + }, | |
179 | + } | |
180 | +}; | |
181 | +</script> |
+++ client/views/pages/user/MediaVideoSearch.vue
... | ... | @@ -0,0 +1,414 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>미디어 영상</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <!-- Bind the image source dynamically for homeicon --> | |
8 | + <li><img :src="homeicon" alt="Home Icon"> | |
9 | + <p>언론에서 바라본 구미시</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>미디어 영상</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <div action="search" class="search-form form "> | |
17 | + <dl> | |
18 | + <dd class="mb-15"> | |
19 | + <p>검색범위</p> | |
20 | + <ul> | |
21 | + <li> | |
22 | + <input type="checkbox" id="allScope" v-model="isChkAllScope" | |
23 | + @change="fnChkAllOptions('scope')" /> | |
24 | + <label for="allScope">전체</label> | |
25 | + </li> | |
26 | + <li v-for="(scope, idx) in searchType" :key="idx"> | |
27 | + <input type="checkbox" :id="idx" :name="searchType" :value="scope.key" | |
28 | + v-model="searchReqDTO.searchType" @change="fnChkOption('scope')" /> | |
29 | + <label :for="idx">{{ scope.value }}</label> | |
30 | + </li> | |
31 | + </ul> | |
32 | + </dd> | |
33 | + <dd class="mb-15"> | |
34 | + <p>검색어</p> | |
35 | + <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div> | |
36 | + </dd> | |
37 | + <dd class="mb-15"> | |
38 | + <p>생산연도</p> | |
39 | + <input type="date" v-model="searchReqDTO.startYear"> | |
40 | + <p class="mark">~</p> | |
41 | + <input type="date" v-model="searchReqDTO.endYear"> | |
42 | + </dd> | |
43 | + <dd class="mb-20"> | |
44 | + <p>카테고리</p> | |
45 | + <ul> | |
46 | + <li v-for="(category, idx) of categorys" :key="idx"> | |
47 | + <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" | |
48 | + v-model="searchReqDTO.searchCtgry" /> | |
49 | + <label :for="category.ctgryId">{{ category.ctgryNm }}</label> | |
50 | + </li> | |
51 | + </ul> | |
52 | + </dd> | |
53 | + <dd class="mb-15"> | |
54 | + <p>정렬</p> | |
55 | + <ul> | |
56 | + <li v-for="(order, idx) of orders" :key="idx"> | |
57 | + <input type="radio" :id="order.key" name="orders" :value="order.key" | |
58 | + v-model="searchReqDTO.order" /> | |
59 | + <label :for="order.key">{{ order.value }}</label> | |
60 | + </li> | |
61 | + </ul> | |
62 | + </dd> | |
63 | + <div class="btn-group"> | |
64 | + <button class="reset"><img :src="reseticon" alt=""> | |
65 | + <p>초기화</p> | |
66 | + </button> | |
67 | + <button class="search"><img :src="searchicon" alt=""> | |
68 | + <p>검색</p> | |
69 | + </button> | |
70 | + </div> | |
71 | + | |
72 | + </dl> | |
73 | + | |
74 | + </div> | |
75 | + <div class="search-result"> | |
76 | + <div class="tabs"> | |
77 | + <div class="flex-sp-bw mb-20 align-center"> | |
78 | + <div class="resultext "> | |
79 | + <img :src="resulticon" alt=""> | |
80 | + <p>총 <b>{{ count }}개</b>의 미디어 영상이 검색되었습니다. </p> | |
81 | + </div> | |
82 | + <div class="flex "> | |
83 | + <ul class="tab-box mb-20"> | |
84 | + <li v-for="(tab, index) in tabs" :key="index" class="tab-title" | |
85 | + :class="{ active: selectedTab === tab.id }" @click="selectTab(tab.id)"> | |
86 | + <img :src="selectedTab === tab.id ? tab.activeImage : tab.inactiveImage" | |
87 | + :alt="tab.title" class="tab-icon" /> | |
88 | + <p><b>{{ tab.title }}</b></p> | |
89 | + </li> | |
90 | + </ul> | |
91 | + <div class="select-box"> | |
92 | + <select v-model="itemsPerPage" @change="changeItemsPerPage"> | |
93 | + <option :value="5" selected>5개</option> | |
94 | + <option :value="10">10개</option> | |
95 | + <option :value="15">15개</option> | |
96 | + </select> | |
97 | + </div> | |
98 | + </div> | |
99 | + | |
100 | + </div> | |
101 | + | |
102 | + <div class="tab-content"> | |
103 | + <!-- Loop through tabContents, and only display content that matches selectedTab --> | |
104 | + <div v-for="(tabContent, idx) in tabContents" :key="idx"> | |
105 | + <!-- Display content only if the tab's ID matches the selectedTab --> | |
106 | + <div v-show="tabContent.id === selectedTab"> | |
107 | + <!-- 카드형 Section (Card Layout) --> | |
108 | + <div v-if="tabContent.viewType === 'card'"> | |
109 | + <ul class="card-wrap"> | |
110 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
111 | + <div class="result-box"> | |
112 | + <!-- Main Image Section --> | |
113 | + <div class="main-img"> | |
114 | + <img :src="resultitem.img" alt="" class="tab-image" /> | |
115 | + </div> | |
116 | + <!-- Text Section --> | |
117 | + <div class="text-box"> | |
118 | + <router-link :to="{ path: '/MediaVideoDetail.page' }"> | |
119 | + <h5>{{ resultitem.title }}</h5> | |
120 | + </router-link> | |
121 | + | |
122 | + <p class="address">{{ resultitem.address }}</p> | |
123 | + <p class="text">{{ resultitem.content }}</p> | |
124 | + | |
125 | + <div class="mb-20"> | |
126 | + <ul class="category"> | |
127 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
128 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
129 | + </ul> | |
130 | + </div> | |
131 | + | |
132 | + <div class="date"> | |
133 | + <ul> | |
134 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
135 | + <li>|</li> | |
136 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
137 | + </ul> | |
138 | + </div> | |
139 | + </div> | |
140 | + </div> | |
141 | + </li> | |
142 | + </ul> | |
143 | + | |
144 | + <!-- Empty State if no results in paginatedItems --> | |
145 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
146 | + <p>등록된 게시물이 없습니다.</p> | |
147 | + </div> | |
148 | + </div> | |
149 | + | |
150 | + <!-- 리스트형 Section (List Layout) --> | |
151 | + <div v-if="tabContent.viewType === 'list'"> | |
152 | + <ul class="list-wrap"> | |
153 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
154 | + <div class="text-box"> | |
155 | + <router-link :to="{ path: '/MediaVideoDetail.page' }"> | |
156 | + <h5>{{ resultitem.title }}</h5> | |
157 | + </router-link> | |
158 | + <p class="address">{{ resultitem.address }}</p> | |
159 | + | |
160 | + <div class="flex-sp-bw"> | |
161 | + <div class="mb-20"> | |
162 | + <ul class="category"> | |
163 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
164 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
165 | + </ul> | |
166 | + </div> | |
167 | + | |
168 | + <div class="date "> | |
169 | + <ul> | |
170 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
171 | + <li>|</li> | |
172 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
173 | + </ul> | |
174 | + </div> | |
175 | + </div> | |
176 | + </div> | |
177 | + </li> | |
178 | + </ul> | |
179 | + | |
180 | + <!-- Empty State if no results in paginatedItems --> | |
181 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
182 | + <p>등록된 게시물이 없습니다.</p> | |
183 | + </div> | |
184 | + </div> | |
185 | + </div> | |
186 | + </div> | |
187 | + </div> | |
188 | + </div> | |
189 | + | |
190 | + <div class="btn-group flex-end mt-40"><button class="register"> <router-link | |
191 | + :to="{ path: '/MediaVideoInsert.page' }">등록</router-link></button></div> | |
192 | + <div class="pagination flex-center mt-40"> | |
193 | + | |
194 | + <!-- Previous and Next Page Buttons --> | |
195 | + <button> | |
196 | + <DoubleLeftOutlined /> | |
197 | + </button> | |
198 | + <button @click="previousPage" :disabled="currentPage === 1"> | |
199 | + <LeftOutlined /> | |
200 | + </button> | |
201 | + <button class="page-number clicked">1</button> | |
202 | + <button @click="nextPage" :disabled="currentPage === totalPages"> | |
203 | + <RightOutlined /> | |
204 | + </button> | |
205 | + <button> | |
206 | + <DoubleRightOutlined /> | |
207 | + </button> | |
208 | + </div> | |
209 | + </div> | |
210 | + </div> | |
211 | + | |
212 | +</template> | |
213 | +<script> | |
214 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
215 | +import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색 | |
216 | + | |
217 | +export default { | |
218 | + components: { | |
219 | + DoubleLeftOutlined, | |
220 | + LeftOutlined, | |
221 | + RightOutlined, | |
222 | + DoubleRightOutlined, | |
223 | + }, | |
224 | + data() { | |
225 | + return { | |
226 | + selectedTab: 1, | |
227 | + // 검색용 객체 | |
228 | + searchReqDTO: { | |
229 | + searchType: [], | |
230 | + searchText: null, | |
231 | + startYear: null, | |
232 | + endYear: null, | |
233 | + searchTy: null, | |
234 | + searchCtgry: [], | |
235 | + order: "rgsde", | |
236 | + }, | |
237 | + tabs: [ | |
238 | + | |
239 | + { | |
240 | + id: 1, | |
241 | + title: "카드형", | |
242 | + activeImage: "client/resources/images/list_icon01_on.png", // Active tab image | |
243 | + inactiveImage: "client/resources/images/list_icon01_off.png", | |
244 | + }, | |
245 | + { | |
246 | + id: 2, | |
247 | + title: "리스트형", | |
248 | + activeImage: "client/resources/images/list_icon02_on.png", // Active tab image | |
249 | + inactiveImage: "client/resources/images/list_icon02_off.png", | |
250 | + }, | |
251 | + ], | |
252 | + tabContents: [ | |
253 | + { id: 1, viewType: 'card', list: [{ sj: 'Item 1', rgsde: '2025-03-01', files: [{ filePath: 'image1.png' }] }] }, | |
254 | + { id: 2, viewType: 'list', list: [{ sj: 'Item 2', rgsde: '2025-03-02', files: [{ filePath: 'image2.png' }] }] }, | |
255 | + ], | |
256 | + paginatedItems: [], | |
257 | + resultitems: [ | |
258 | + { | |
259 | + img: 'client/resources/images/img6.png', | |
260 | + title: '미디어 영상 제목', | |
261 | + address: '경상북도 구미시 송정대로 55', | |
262 | + content: '대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어…', | |
263 | + category1: true, | |
264 | + category2: true, | |
265 | + year: 2020, | |
266 | + date: '2021-01-01' | |
267 | + }, | |
268 | + | |
269 | + ], | |
270 | + currentPage: 1, // Current page number | |
271 | + itemsPerPage: 5, | |
272 | + resulticon: "client/resources/images/icon/r-check.png", | |
273 | + homeicon: 'client/resources/images/icon/home.png', | |
274 | + searchicon: 'client/resources/images/icon/search.png', | |
275 | + reseticon: 'client/resources/images/icon/reset.png', | |
276 | + righticon: 'client/resources/images/icon/right.png', | |
277 | + count: 23, | |
278 | + checkOptions: [ | |
279 | + '전체', | |
280 | + '사진', | |
281 | + '영상', | |
282 | + '미디어 영상', | |
283 | + '보도자료', | |
284 | + ], | |
285 | + checkOptions2: [ | |
286 | + '전체', | |
287 | + '제목', | |
288 | + '내용', | |
289 | + '주소', | |
290 | + ], | |
291 | + checkOptions3: [ | |
292 | + '카테고리1', | |
293 | + '카테고리2', | |
294 | + '카테고리3', | |
295 | + '카테고리4', | |
296 | + '카테고리5', | |
297 | + ], | |
298 | + checkOptions4: [ | |
299 | + '최신', | |
300 | + '인기', | |
301 | + ], | |
302 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
303 | + searchType: [ | |
304 | + { key: "sj", value: "제목" }, | |
305 | + { key: "cn", value: "내용" }, | |
306 | + { key: "adres", value: "주소" }, | |
307 | + ], // 검색범위 목록 | |
308 | + categorys: [], // 카테고리 목록 | |
309 | + orders: [ | |
310 | + { key: "rgsde", value: "최신" }, | |
311 | + { key: "rdcnt", value: "인기" }, | |
312 | + ], // 정렬 목록 | |
313 | + }; | |
314 | + }, | |
315 | + computed: { | |
316 | + // Total number of pages | |
317 | + totalPages() { | |
318 | + return Math.ceil(this.resultitems.length / this.itemsPerPage); | |
319 | + }, | |
320 | + | |
321 | + // Paginated items based on current page and items per page | |
322 | + paginatedItems() { | |
323 | + const start = (this.currentPage - 1) * this.itemsPerPage; | |
324 | + const end = start + this.itemsPerPage; | |
325 | + return this.resultitems.slice(start, end); | |
326 | + }, | |
327 | + }, | |
328 | + created() { | |
329 | + // 초기 데이터 세팅 | |
330 | + this.isChkAllScope = true; | |
331 | + this.searchReqDTO.searchType = this.searchType.map(item => item.key); | |
332 | + this.searchReqDTO.order = this.orders[0].key | |
333 | + | |
334 | + this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음) | |
335 | + }, | |
336 | + methods: { | |
337 | + selectTab(tabId) { | |
338 | + this.selectedTab = tabId; // Update the selected tab index | |
339 | + }, | |
340 | + // Change the number of items displayed per page | |
341 | + changeItemsPerPage() { | |
342 | + this.currentPage = 1; // Reset to first page when changing items per page | |
343 | + }, | |
344 | + previousPage() { | |
345 | + if (this.currentPage > 1) { | |
346 | + this.currentPage--; | |
347 | + } | |
348 | + }, | |
349 | + | |
350 | + // Go to the next page | |
351 | + nextPage() { | |
352 | + if (this.currentPage < this.totalPages) { | |
353 | + this.currentPage++; | |
354 | + } | |
355 | + }, | |
356 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
357 | + searchType: [ | |
358 | + { key: "sj", value: "제목" }, | |
359 | + { key: "cn", value: "내용" }, | |
360 | + { key: "adres", value: "주소" }, | |
361 | + ], // 검색범위 목록 | |
362 | + categorys: [], // 카테고리 목록 | |
363 | + orders: [ | |
364 | + { key: "rgsde", value: "최신" }, | |
365 | + { key: "rdcnt", value: "인기" }, | |
366 | + ], // 정렬 목록 | |
367 | + | |
368 | + async fnFindCategorys() { | |
369 | + try { | |
370 | + const response = await findAllCategoryProc(); | |
371 | + this.categorys = response.data.data.ctgry; | |
372 | + } catch (error) { | |
373 | + if (error.response) { | |
374 | + console.log("에러 응답:", error.response.data); | |
375 | + } | |
376 | + console.error("Error:", error); | |
377 | + } | |
378 | + }, | |
379 | + | |
380 | + // 통합검색 | |
381 | + async fnFindAllDatas() { | |
382 | + try { | |
383 | + let params = {}; | |
384 | + if (this.searchReqDTO.searchRecord.length > 0) { | |
385 | + params.searchRecords = this.searchReqDTO.searchRecord.join(','); | |
386 | + } | |
387 | + if (this.searchReqDTO.searchType.length > 0) { | |
388 | + params.searchTypes = this.searchReqDTO.searchType.join(','); | |
389 | + } | |
390 | + if (this.searchReqDTO.searchCtgry.length > 0) { | |
391 | + params.searchCtgries = this.searchReqDTO.searchCtgry.join(','); | |
392 | + } | |
393 | + params.searchText = this.searchReqDTO.searchText; | |
394 | + params.startYear = this.searchReqDTO.startYear; | |
395 | + params.endYear = this.searchReqDTO.endYear; | |
396 | + params.order = this.searchReqDTO.order; | |
397 | + | |
398 | + // API 호출 | |
399 | + const response = await findAllDatas(params); | |
400 | + this.searchResult = response.data.data.searchResult; | |
401 | + } catch (error) { | |
402 | + if (error.response) { | |
403 | + console.log("에러 응답:", error.response.data); | |
404 | + } | |
405 | + console.error("Error:", error); | |
406 | + } | |
407 | + }, | |
408 | + }, | |
409 | + | |
410 | + | |
411 | + | |
412 | +}; | |
413 | +</script> | |
414 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/BodoDetail.vue
+++ client/views/pages/user/NewsReleaseDetail.vue
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 |
</div> |
14 | 14 |
</div> |
15 | 15 |
<form action="" class="gallery-form mb-40"> |
16 |
- <dl class="mb-20"> |
|
16 |
+ <dl class="mb-60"> |
|
17 | 17 |
<dd> |
18 | 18 |
<p>보도자료 제목1 |
19 | 19 |
</p> |
... | ... | @@ -24,35 +24,38 @@ |
24 | 24 |
</dd> |
25 | 25 |
|
26 | 26 |
</dl> |
27 |
- <div> |
|
28 |
- <div> |
|
27 |
+ <div class="flex-sp-bw mb-50"> |
|
28 |
+ <div class="img-box"> |
|
29 | 29 |
<img :src="noimg" alt=""> |
30 | 30 |
</div> |
31 |
- <div action="" class="info-form mb-50"> |
|
32 |
- <h3>기본정보</h3> |
|
33 |
- <dl> |
|
34 |
- <dd class="mb-20"> |
|
35 |
- <img :src="addressicon" alt=""> |
|
36 |
- <span>주소</span> |
|
37 |
- <p>경상북도 구미시 송정대로 55</p> |
|
38 |
- </dd> |
|
39 |
- <dd class="mb-20"> |
|
40 |
- <img :src="yearicon" alt=""> |
|
41 |
- <span>생산정보</span> |
|
42 |
- <p>2017</p> |
|
43 |
- |
|
44 |
- </dd> |
|
45 |
- <dd> |
|
46 |
- <img :src="categoryicon" alt=""> |
|
47 |
- <span>카테고리</span> |
|
48 |
- <ul class="category"> |
|
49 |
- <li v-if="resultitem.category1" class="category1">카테고리1</li> |
|
50 |
- <li v-if="resultitem.category2" class="category2">카테고리2</li> |
|
51 |
- </ul> |
|
52 |
- |
|
53 |
- </dd> |
|
54 |
- |
|
55 |
- </dl> |
|
31 |
+ <div class="info-form wfull"> |
|
32 |
+ <div class="info-box"> |
|
33 |
+ <h3>기본정보</h3> |
|
34 |
+ <dl> |
|
35 |
+ <dd class="mb-20"> |
|
36 |
+ <img :src="addressicon" alt=""> |
|
37 |
+ <span>링크</span> |
|
38 |
+ <p>https://news.sbs.co.kr/news/endPage.do?news_id=N100800 |
|
39 |
+ 9901&plink=STAND&cooper=NAVER</p> |
|
40 |
+ </dd> |
|
41 |
+ <dd class="mb-20"> |
|
42 |
+ <img :src="yearicon" alt=""> |
|
43 |
+ <span>생산연도</span> |
|
44 |
+ <p>2017</p> |
|
45 |
+ |
|
46 |
+ </dd> |
|
47 |
+ <dd> |
|
48 |
+ <img :src="categoryicon" alt=""> |
|
49 |
+ <span>카테고리</span> |
|
50 |
+ <ul class="category"> |
|
51 |
+ <li v-if="resultitem.category1" class="category1">카테고리1</li> |
|
52 |
+ <li v-if="resultitem.category2" class="category2">카테고리2</li> |
|
53 |
+ </ul> |
|
54 |
+ |
|
55 |
+ </dd> |
|
56 |
+ |
|
57 |
+ </dl> |
|
58 |
+ </div> |
|
56 | 59 |
</div> |
57 | 60 |
</div> |
58 | 61 |
</form> |
... | ... | @@ -70,9 +73,9 @@ |
70 | 73 |
|
71 | 74 |
|
72 | 75 |
<div class="btn-group flex-center"> |
73 |
- <button class="delete w130" type="button" @click="fnDeleteUser">삭제</button> |
|
74 |
- <button class="update w130" type="button" @click="fnUpdateUser">수정</button> |
|
75 |
- <button class="list w130" type="button" @click="fnUpdateUser">목록</button> |
|
76 |
+ <button class="red-line w130" type="button" @click="fnDeleteUser">삭제</button> |
|
77 |
+ <button class="blue-line w130" type="button" @click="fnUpdateUser">수정</button> |
|
78 |
+ <button class="gray-line-bg w130" type="button" @click="fnUpdateUser">목록</button> |
|
76 | 79 |
</div> |
77 | 80 |
</div> |
78 | 81 |
</template> |
+++ client/views/pages/user/NewsReleaseInsert.vue
... | ... | @@ -0,0 +1,212 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>영상 기록물</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li><img :src="homeicon" alt="Home Icon"> | |
8 | + <p>기록물</p> | |
9 | + </li> | |
10 | + <li><img :src="righticon" alt=""></li> | |
11 | + <li>영상 기록물</li> | |
12 | + </ul> | |
13 | + </div> | |
14 | + </div> | |
15 | + <form action="" class="insert-form mb-50"> | |
16 | + <dl> | |
17 | + <dd> | |
18 | + <label for="id" class="require">제목</label> | |
19 | + <div class="wfull"><input type="text" id="id" placeholder="제목을 입력하세요."></div> | |
20 | + </dd> | |
21 | + <div class="hr"></div> | |
22 | + <dd> | |
23 | + <label for="year">생산연도</label> | |
24 | + <input type="text" id="year" placeholder="생산연도를 입력하세요"> | |
25 | + </dd> | |
26 | + <div class="hr"></div> | |
27 | + <dd> | |
28 | + <label for="address">주소</label> | |
29 | + <div class="wfull"><input type="text" id="address" placeholder="주소를 입력하세요"></div> | |
30 | + </dd> | |
31 | + <div class="hr"></div> | |
32 | + <dd> | |
33 | + <label for="text">내용</label> | |
34 | + <div class="wfull"> | |
35 | + <EditorComponent :contents="insertDTO.cn" /> | |
36 | + </div> | |
37 | + </dd> | |
38 | + <div class="hr"></div> | |
39 | + <dd> | |
40 | + <label for="category" class="flex align-center"> | |
41 | + <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button> | |
42 | + </label> | |
43 | + <ul class="category"> | |
44 | + <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button> | |
45 | + </li> | |
46 | + </ul> | |
47 | + </dd> | |
48 | + <div class="hr"></div> | |
49 | + <dd> | |
50 | + <label for="file" class="require">파일</label> | |
51 | + <ul class="wfull"> | |
52 | + <li class="flex align-center"> | |
53 | + <p>파일첨부</p> | |
54 | + <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</span></div> | |
55 | + </li> | |
56 | + <li class="file-insert"> | |
57 | + <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames"> | |
58 | + <label for="fileInput" class="file-label mb-20"> | |
59 | + <div class="flex-center align-center"><img :src="fileicon" alt=""> | |
60 | + <p>파일첨부하기</p> | |
61 | + </div> | |
62 | + <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p> | |
63 | + </label> | |
64 | + <p class="mb-10">파일목록</p> | |
65 | + <div id="fileNames" class="file-names"> | |
66 | + <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span> | |
67 | + <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap"> | |
68 | + <div class="file-name"> | |
69 | + <!-- Corrected here: Use file.icon instead of fileicons.img --> | |
70 | + <img :src="file.icon" alt="fileicon"> | |
71 | + <p>{{ file.name }}</p> | |
72 | + </div> | |
73 | + <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button> | |
74 | + </div> | |
75 | + </div> | |
76 | + </li> | |
77 | + </ul> | |
78 | + </dd> | |
79 | + </dl> | |
80 | + </form> | |
81 | + <div class="btn-group flex-center"> | |
82 | + <button class="cancel">취소</button> | |
83 | + <button class="register">등록</button> | |
84 | + </div> | |
85 | + </div> | |
86 | + <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" /> | |
87 | +</template> | |
88 | +<script> | |
89 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
90 | +// COMPONENT | |
91 | +import EditorComponent from '../../component/EditorComponent.vue'; | |
92 | +import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; | |
93 | + | |
94 | +export default { | |
95 | + components: { | |
96 | + DoubleLeftOutlined, | |
97 | + LeftOutlined, | |
98 | + RightOutlined, | |
99 | + DoubleRightOutlined, | |
100 | + EditorComponent, CategorySelectModal, | |
101 | + }, | |
102 | + | |
103 | + data() { | |
104 | + return { | |
105 | + // Define the image sources | |
106 | + homeicon: 'client/resources/images/icon/home.png', | |
107 | + erroricon: 'client/resources/images/icon/error.png', | |
108 | + righticon: 'client/resources/images/icon/right.png', | |
109 | + fileicon: 'client/resources/images/icon/file.png', | |
110 | + searchicon: 'client/resources/images/icon/search.png', | |
111 | + | |
112 | + isModalOpen: false, | |
113 | + | |
114 | + items: [ | |
115 | + { id: 1, category: '카테고리 1', selected: false }, | |
116 | + { id: 2, category: '카테고리 2', selected: false }, | |
117 | + { id: 3, category: '카테고리 3', selected: false }, | |
118 | + ], | |
119 | + fileNames: [], | |
120 | + insertDTO: { | |
121 | + sj: null, //제목 | |
122 | + cn: null, //내용 | |
123 | + adres: null, // 주소 | |
124 | + prdctnYear: null, // 생산연도 | |
125 | + ty: 'P', // 타입 ( P: 사진, V: 영상 ) | |
126 | + multipartFiles: null, // 첨부파일 정보 | |
127 | + ctgryIds: null, // 카테고리 정보 | |
128 | + }, | |
129 | + | |
130 | + files: [], | |
131 | + selectedCtgries: [], // 카테고리 목록 | |
132 | + }; | |
133 | + }, | |
134 | + computed: { | |
135 | + filteredItems() { | |
136 | + // This could be modified to support filtering based on searchQuery | |
137 | + return this.items.filter(item => | |
138 | + item.category.includes(this.searchQuery) | |
139 | + ); | |
140 | + } | |
141 | + }, | |
142 | + created() { | |
143 | + }, | |
144 | + methods: { | |
145 | + registerCategories() { | |
146 | + // Add selected categories to the displayed list | |
147 | + this.selectedCtgries = this.items | |
148 | + .filter(item => item.selected) | |
149 | + .map(item => item.category); | |
150 | + this.closeModal(); // Close modal after registration | |
151 | + }, | |
152 | + removeCategory(index) { | |
153 | + // Remove category from the list | |
154 | + this.selectedCtgries.splice(index, 1); | |
155 | + }, | |
156 | + searchCategories() { | |
157 | + // You can implement search logic if needed | |
158 | + }, | |
159 | + nextPage() { | |
160 | + if (this.currentPage < this.totalPages) { | |
161 | + this.currentPage++; | |
162 | + } | |
163 | + }, | |
164 | + previousPage() { | |
165 | + if (this.currentPage > 1) { | |
166 | + this.currentPage--; | |
167 | + } | |
168 | + }, | |
169 | + showFileNames(event) { | |
170 | + const files = event.target.files; | |
171 | + this.fileNames = []; // Clear previous file names | |
172 | + | |
173 | + for (let i = 0; i < files.length; i++) { | |
174 | + const file = files[i]; | |
175 | + const fileType = file.name.split('.').pop().toLowerCase(); // Get file extension | |
176 | + | |
177 | + // Set default icon | |
178 | + let iconPath = this.fileicons; | |
179 | + | |
180 | + // Determine the icon based on file type | |
181 | + if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) { | |
182 | + iconPath = 'client/resources/images/icon/imgicon.png'; // Example for image files | |
183 | + } else if (['pdf'].includes(fileType)) { | |
184 | + iconPath = 'client/resources/images/icon/pdficon.png'; // Example for PDF files | |
185 | + } else if (['xls'].includes(fileType)) { | |
186 | + iconPath = 'client/resources/images/icon/excelicon.png'; // Example for audio files | |
187 | + } else if (['hwp'].includes(fileType)) { | |
188 | + iconPath = 'client/resources/images/icon/hwpicon.png'; // Example for video files | |
189 | + } | |
190 | + | |
191 | + // Push the file name and corresponding icon to the fileNames array | |
192 | + this.fileNames.push({ | |
193 | + name: file.name, | |
194 | + icon: iconPath | |
195 | + }); | |
196 | + } | |
197 | + }, | |
198 | + removeFile(index) { | |
199 | + // Remove file from the list | |
200 | + this.fileNames.splice(index, 1); | |
201 | + console.log(removeFile) | |
202 | + }, | |
203 | + openModal() { | |
204 | + this.isModalOpen = true; | |
205 | + }, | |
206 | + // 모달 닫기 | |
207 | + closeModal() { | |
208 | + this.isModalOpen = false; | |
209 | + }, | |
210 | + } | |
211 | +}; | |
212 | +</script> |
+++ client/views/pages/user/NewsReleaseSearch.vue
... | ... | @@ -0,0 +1,414 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>보도자료</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <!-- Bind the image source dynamically for homeicon --> | |
8 | + <li><img :src="homeicon" alt="Home Icon"> | |
9 | + <p>언론에서 바라본 구미시</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>보도자료</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <div action="search" class="search-form form "> | |
17 | + <dl> | |
18 | + <dd class="mb-15"> | |
19 | + <p>검색범위</p> | |
20 | + <ul> | |
21 | + <li> | |
22 | + <input type="checkbox" id="allScope" v-model="isChkAllScope" | |
23 | + @change="fnChkAllOptions('scope')" /> | |
24 | + <label for="allScope">전체</label> | |
25 | + </li> | |
26 | + <li v-for="(scope, idx) in searchType" :key="idx"> | |
27 | + <input type="checkbox" :id="idx" :name="searchType" :value="scope.key" | |
28 | + v-model="searchReqDTO.searchType" @change="fnChkOption('scope')" /> | |
29 | + <label :for="idx">{{ scope.value }}</label> | |
30 | + </li> | |
31 | + </ul> | |
32 | + </dd> | |
33 | + <dd class="mb-15"> | |
34 | + <p>검색어</p> | |
35 | + <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div> | |
36 | + </dd> | |
37 | + <dd class="mb-15"> | |
38 | + <p>생산연도</p> | |
39 | + <input type="date" v-model="searchReqDTO.startYear"> | |
40 | + <p class="mark">~</p> | |
41 | + <input type="date" v-model="searchReqDTO.endYear"> | |
42 | + </dd> | |
43 | + <dd class="mb-20"> | |
44 | + <p>카테고리</p> | |
45 | + <ul> | |
46 | + <li v-for="(category, idx) of categorys" :key="idx"> | |
47 | + <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" | |
48 | + v-model="searchReqDTO.searchCtgry" /> | |
49 | + <label :for="category.ctgryId">{{ category.ctgryNm }}</label> | |
50 | + </li> | |
51 | + </ul> | |
52 | + </dd> | |
53 | + <dd class="mb-15"> | |
54 | + <p>정렬</p> | |
55 | + <ul> | |
56 | + <li v-for="(order, idx) of orders" :key="idx"> | |
57 | + <input type="radio" :id="order.key" name="orders" :value="order.key" | |
58 | + v-model="searchReqDTO.order" /> | |
59 | + <label :for="order.key">{{ order.value }}</label> | |
60 | + </li> | |
61 | + </ul> | |
62 | + </dd> | |
63 | + <div class="btn-group"> | |
64 | + <button class="reset"><img :src="reseticon" alt=""> | |
65 | + <p>초기화</p> | |
66 | + </button> | |
67 | + <button class="search"><img :src="searchicon" alt=""> | |
68 | + <p>검색</p> | |
69 | + </button> | |
70 | + </div> | |
71 | + | |
72 | + </dl> | |
73 | + | |
74 | + </div> | |
75 | + <div class="search-result"> | |
76 | + <div class="tabs"> | |
77 | + <div class="flex-sp-bw mb-20 align-center"> | |
78 | + <div class="resultext "> | |
79 | + <img :src="resulticon" alt=""> | |
80 | + <p>총 <b>{{ count }}개</b>의 보도자료가 검색되었습니다. </p> | |
81 | + </div> | |
82 | + <div class="flex "> | |
83 | + <ul class="tab-box mb-20"> | |
84 | + <li v-for="(tab, index) in tabs" :key="index" class="tab-title" | |
85 | + :class="{ active: selectedTab === tab.id }" @click="selectTab(tab.id)"> | |
86 | + <img :src="selectedTab === tab.id ? tab.activeImage : tab.inactiveImage" | |
87 | + :alt="tab.title" class="tab-icon" /> | |
88 | + <p><b>{{ tab.title }}</b></p> | |
89 | + </li> | |
90 | + </ul> | |
91 | + <div class="select-box"> | |
92 | + <select v-model="itemsPerPage" @change="changeItemsPerPage"> | |
93 | + <option :value="5" selected>5개</option> | |
94 | + <option :value="10">10개</option> | |
95 | + <option :value="15">15개</option> | |
96 | + </select> | |
97 | + </div> | |
98 | + </div> | |
99 | + | |
100 | + </div> | |
101 | + | |
102 | + <div class="tab-content"> | |
103 | + <!-- Loop through tabContents, and only display content that matches selectedTab --> | |
104 | + <div v-for="(tabContent, idx) in tabContents" :key="idx"> | |
105 | + <!-- Display content only if the tab's ID matches the selectedTab --> | |
106 | + <div v-show="tabContent.id === selectedTab"> | |
107 | + <!-- 카드형 Section (Card Layout) --> | |
108 | + <div v-if="tabContent.viewType === 'card'"> | |
109 | + <ul class="card-wrap"> | |
110 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
111 | + <div class="result-box"> | |
112 | + <!-- Main Image Section --> | |
113 | + <div class="main-img"> | |
114 | + <img :src="resultitem.img" alt="" class="tab-image" /> | |
115 | + </div> | |
116 | + <!-- Text Section --> | |
117 | + <div class="text-box"> | |
118 | + <router-link :to="{ path: '/NewsReleaseDetail.page' }"> | |
119 | + <h5>{{ resultitem.title }}</h5> | |
120 | + </router-link> | |
121 | + | |
122 | + <p class="address">{{ resultitem.address }}</p> | |
123 | + <p class="text">{{ resultitem.content }}</p> | |
124 | + | |
125 | + <div class="mb-20"> | |
126 | + <ul class="category"> | |
127 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
128 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
129 | + </ul> | |
130 | + </div> | |
131 | + | |
132 | + <div class="date"> | |
133 | + <ul> | |
134 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
135 | + <li>|</li> | |
136 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
137 | + </ul> | |
138 | + </div> | |
139 | + </div> | |
140 | + </div> | |
141 | + </li> | |
142 | + </ul> | |
143 | + | |
144 | + <!-- Empty State if no results in paginatedItems --> | |
145 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
146 | + <p>등록된 게시물이 없습니다.</p> | |
147 | + </div> | |
148 | + </div> | |
149 | + | |
150 | + <!-- 리스트형 Section (List Layout) --> | |
151 | + <div v-if="tabContent.viewType === 'list'"> | |
152 | + <ul class="list-wrap"> | |
153 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
154 | + <div class="text-box"> | |
155 | + <router-link :to="{ path: '/NewsReleaseDetail.page' }"> | |
156 | + <h5>{{ resultitem.title }}</h5> | |
157 | + </router-link> | |
158 | + <p class="address">{{ resultitem.address }}</p> | |
159 | + | |
160 | + <div class="flex-sp-bw"> | |
161 | + <div class="mb-20"> | |
162 | + <ul class="category"> | |
163 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
164 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
165 | + </ul> | |
166 | + </div> | |
167 | + | |
168 | + <div class="date "> | |
169 | + <ul> | |
170 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
171 | + <li>|</li> | |
172 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
173 | + </ul> | |
174 | + </div> | |
175 | + </div> | |
176 | + </div> | |
177 | + </li> | |
178 | + </ul> | |
179 | + | |
180 | + <!-- Empty State if no results in paginatedItems --> | |
181 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
182 | + <p>등록된 게시물이 없습니다.</p> | |
183 | + </div> | |
184 | + </div> | |
185 | + </div> | |
186 | + </div> | |
187 | + </div> | |
188 | + </div> | |
189 | + | |
190 | + <div class="btn-group flex-end mt-40"><button class="register"> <router-link | |
191 | + :to="{ path: '/PicHistoryInsert.page' }">등록</router-link></button></div> | |
192 | + <div class="pagination flex-center mt-40"> | |
193 | + | |
194 | + <!-- Previous and Next Page Buttons --> | |
195 | + <button> | |
196 | + <DoubleLeftOutlined /> | |
197 | + </button> | |
198 | + <button @click="previousPage" :disabled="currentPage === 1"> | |
199 | + <LeftOutlined /> | |
200 | + </button> | |
201 | + <button class="page-number clicked">1</button> | |
202 | + <button @click="nextPage" :disabled="currentPage === totalPages"> | |
203 | + <RightOutlined /> | |
204 | + </button> | |
205 | + <button> | |
206 | + <DoubleRightOutlined /> | |
207 | + </button> | |
208 | + </div> | |
209 | + </div> | |
210 | + </div> | |
211 | + | |
212 | +</template> | |
213 | +<script> | |
214 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
215 | +import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색 | |
216 | + | |
217 | +export default { | |
218 | + components: { | |
219 | + DoubleLeftOutlined, | |
220 | + LeftOutlined, | |
221 | + RightOutlined, | |
222 | + DoubleRightOutlined, | |
223 | + }, | |
224 | + data() { | |
225 | + return { | |
226 | + selectedTab: 1, | |
227 | + // 검색용 객체 | |
228 | + searchReqDTO: { | |
229 | + searchType: [], | |
230 | + searchText: null, | |
231 | + startYear: null, | |
232 | + endYear: null, | |
233 | + searchTy: null, | |
234 | + searchCtgry: [], | |
235 | + order: "rgsde", | |
236 | + }, | |
237 | + tabs: [ | |
238 | + | |
239 | + { | |
240 | + id: 1, | |
241 | + title: "카드형", | |
242 | + activeImage: "client/resources/images/list_icon01_on.png", // Active tab image | |
243 | + inactiveImage: "client/resources/images/list_icon01_off.png", | |
244 | + }, | |
245 | + { | |
246 | + id: 2, | |
247 | + title: "리스트형", | |
248 | + activeImage: "client/resources/images/list_icon02_on.png", // Active tab image | |
249 | + inactiveImage: "client/resources/images/list_icon02_off.png", | |
250 | + }, | |
251 | + ], | |
252 | + tabContents: [ | |
253 | + { id: 1, viewType: 'card', list: [{ sj: 'Item 1', rgsde: '2025-03-01', files: [{ filePath: 'image1.png' }] }] }, | |
254 | + { id: 2, viewType: 'list', list: [{ sj: 'Item 2', rgsde: '2025-03-02', files: [{ filePath: 'image2.png' }] }] }, | |
255 | + ], | |
256 | + paginatedItems: [], | |
257 | + resultitems: [ | |
258 | + { | |
259 | + img: 'client/resources/images/img6.png', | |
260 | + title: '보도자료 제목', | |
261 | + address: '경상북도 구미시 송정대로 55', | |
262 | + content: '대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어…', | |
263 | + category1: true, | |
264 | + category2: true, | |
265 | + year: 2020, | |
266 | + date: '2021-01-01' | |
267 | + }, | |
268 | + | |
269 | + ], | |
270 | + currentPage: 1, // Current page number | |
271 | + itemsPerPage: 5, | |
272 | + resulticon: "client/resources/images/icon/r-check.png", | |
273 | + homeicon: 'client/resources/images/icon/home.png', | |
274 | + searchicon: 'client/resources/images/icon/search.png', | |
275 | + reseticon: 'client/resources/images/icon/reset.png', | |
276 | + righticon: 'client/resources/images/icon/right.png', | |
277 | + count: 23, | |
278 | + checkOptions: [ | |
279 | + '전체', | |
280 | + '사진', | |
281 | + '영상', | |
282 | + '미디어 영상', | |
283 | + '보도자료', | |
284 | + ], | |
285 | + checkOptions2: [ | |
286 | + '전체', | |
287 | + '제목', | |
288 | + '내용', | |
289 | + '주소', | |
290 | + ], | |
291 | + checkOptions3: [ | |
292 | + '카테고리1', | |
293 | + '카테고리2', | |
294 | + '카테고리3', | |
295 | + '카테고리4', | |
296 | + '카테고리5', | |
297 | + ], | |
298 | + checkOptions4: [ | |
299 | + '최신', | |
300 | + '인기', | |
301 | + ], | |
302 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
303 | + searchType: [ | |
304 | + { key: "sj", value: "제목" }, | |
305 | + { key: "cn", value: "내용" }, | |
306 | + { key: "adres", value: "주소" }, | |
307 | + ], // 검색범위 목록 | |
308 | + categorys: [], // 카테고리 목록 | |
309 | + orders: [ | |
310 | + { key: "rgsde", value: "최신" }, | |
311 | + { key: "rdcnt", value: "인기" }, | |
312 | + ], // 정렬 목록 | |
313 | + }; | |
314 | + }, | |
315 | + computed: { | |
316 | + // Total number of pages | |
317 | + totalPages() { | |
318 | + return Math.ceil(this.resultitems.length / this.itemsPerPage); | |
319 | + }, | |
320 | + | |
321 | + // Paginated items based on current page and items per page | |
322 | + paginatedItems() { | |
323 | + const start = (this.currentPage - 1) * this.itemsPerPage; | |
324 | + const end = start + this.itemsPerPage; | |
325 | + return this.resultitems.slice(start, end); | |
326 | + }, | |
327 | + }, | |
328 | + created() { | |
329 | + // 초기 데이터 세팅 | |
330 | + this.isChkAllScope = true; | |
331 | + this.searchReqDTO.searchType = this.searchType.map(item => item.key); | |
332 | + this.searchReqDTO.order = this.orders[0].key | |
333 | + | |
334 | + this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음) | |
335 | + }, | |
336 | + methods: { | |
337 | + selectTab(tabId) { | |
338 | + this.selectedTab = tabId; // Update the selected tab index | |
339 | + }, | |
340 | + // Change the number of items displayed per page | |
341 | + changeItemsPerPage() { | |
342 | + this.currentPage = 1; // Reset to first page when changing items per page | |
343 | + }, | |
344 | + previousPage() { | |
345 | + if (this.currentPage > 1) { | |
346 | + this.currentPage--; | |
347 | + } | |
348 | + }, | |
349 | + | |
350 | + // Go to the next page | |
351 | + nextPage() { | |
352 | + if (this.currentPage < this.totalPages) { | |
353 | + this.currentPage++; | |
354 | + } | |
355 | + }, | |
356 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
357 | + searchType: [ | |
358 | + { key: "sj", value: "제목" }, | |
359 | + { key: "cn", value: "내용" }, | |
360 | + { key: "adres", value: "주소" }, | |
361 | + ], // 검색범위 목록 | |
362 | + categorys: [], // 카테고리 목록 | |
363 | + orders: [ | |
364 | + { key: "rgsde", value: "최신" }, | |
365 | + { key: "rdcnt", value: "인기" }, | |
366 | + ], // 정렬 목록 | |
367 | + | |
368 | + async fnFindCategorys() { | |
369 | + try { | |
370 | + const response = await findAllCategoryProc(); | |
371 | + this.categorys = response.data.data.ctgry; | |
372 | + } catch (error) { | |
373 | + if (error.response) { | |
374 | + console.log("에러 응답:", error.response.data); | |
375 | + } | |
376 | + console.error("Error:", error); | |
377 | + } | |
378 | + }, | |
379 | + | |
380 | + // 통합검색 | |
381 | + async fnFindAllDatas() { | |
382 | + try { | |
383 | + let params = {}; | |
384 | + if (this.searchReqDTO.searchRecord.length > 0) { | |
385 | + params.searchRecords = this.searchReqDTO.searchRecord.join(','); | |
386 | + } | |
387 | + if (this.searchReqDTO.searchType.length > 0) { | |
388 | + params.searchTypes = this.searchReqDTO.searchType.join(','); | |
389 | + } | |
390 | + if (this.searchReqDTO.searchCtgry.length > 0) { | |
391 | + params.searchCtgries = this.searchReqDTO.searchCtgry.join(','); | |
392 | + } | |
393 | + params.searchText = this.searchReqDTO.searchText; | |
394 | + params.startYear = this.searchReqDTO.startYear; | |
395 | + params.endYear = this.searchReqDTO.endYear; | |
396 | + params.order = this.searchReqDTO.order; | |
397 | + | |
398 | + // API 호출 | |
399 | + const response = await findAllDatas(params); | |
400 | + this.searchResult = response.data.data.searchResult; | |
401 | + } catch (error) { | |
402 | + if (error.response) { | |
403 | + console.log("에러 응답:", error.response.data); | |
404 | + } | |
405 | + console.error("Error:", error); | |
406 | + } | |
407 | + }, | |
408 | + }, | |
409 | + | |
410 | + | |
411 | + | |
412 | +}; | |
413 | +</script> | |
414 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
--- client/views/pages/user/PicHistoryDetail.vue
+++ client/views/pages/user/PicHistoryDetail.vue
... | ... | @@ -74,7 +74,7 @@ |
74 | 74 |
</dd> |
75 | 75 |
<dd class="mb-20"> |
76 | 76 |
<img :src="yearicon" alt=""> |
77 |
- <span>생산정보</span> |
|
77 |
+ <span>생산연도</span> |
|
78 | 78 |
<p>2017</p> |
79 | 79 |
|
80 | 80 |
</dd> |
... | ... | @@ -91,9 +91,9 @@ |
91 | 91 |
</dl> |
92 | 92 |
</form> |
93 | 93 |
<div class="btn-group flex-center"> |
94 |
- <button class="delete w130" type="button" @click="fnDeleteUser">삭제</button> |
|
95 |
- <button class="update w130" type="button" @click="fnUpdateUser">수정</button> |
|
96 |
- <button class="list w130" type="button" @click="fnUpdateUser">목록</button> |
|
94 |
+ <button class="red-line " type="button" @click="fnDeleteUser">삭제</button> |
|
95 |
+ <button class="blue-line " type="button" @click="fnUpdateUser">수정</button> |
|
96 |
+ <button class="gray-line-bg " type="button" @click="fnUpdateUser">목록</button> |
|
97 | 97 |
</div> |
98 | 98 |
</div> |
99 | 99 |
</template> |
--- client/views/pages/user/PicHistorySearch.vue
+++ client/views/pages/user/PicHistorySearch.vue
... | ... | @@ -9,11 +9,11 @@ |
9 | 9 |
<p>기록물</p> |
10 | 10 |
</li> |
11 | 11 |
<li><img :src="righticon" alt=""></li> |
12 |
- <li>통합검색</li> |
|
12 |
+ <li>사진 기록물</li> |
|
13 | 13 |
</ul> |
14 | 14 |
</div> |
15 | 15 |
</div> |
16 |
- <form action="search" class="search-form mb-40"> |
|
16 |
+ <div action="search" class="search-form form "> |
|
17 | 17 |
<dl> |
18 | 18 |
<dd class="mb-15"> |
19 | 19 |
<p>검색범위</p> |
... | ... | @@ -71,7 +71,7 @@ |
71 | 71 |
|
72 | 72 |
</dl> |
73 | 73 |
|
74 |
- </form> |
|
74 |
+ </div> |
|
75 | 75 |
<div class="search-result"> |
76 | 76 |
<div class="tabs"> |
77 | 77 |
<div class="flex-sp-bw mb-20 align-center"> |
... | ... | @@ -237,14 +237,14 @@ |
237 | 237 |
{ |
238 | 238 |
id: 1, |
239 | 239 |
title: "카드형", |
240 |
- activeImage: "client/resources/images/mCont_ico1_on.png", // Active tab image |
|
241 |
- inactiveImage: "client/resources/images/mCont_ico1_off.png", |
|
240 |
+ activeImage: "client/resources/images/list_icon01_on.png", // Active tab image |
|
241 |
+ inactiveImage: "client/resources/images/list_icon01_off.png", |
|
242 | 242 |
}, |
243 | 243 |
{ |
244 | 244 |
id: 2, |
245 | 245 |
title: "리스트형", |
246 |
- activeImage: "client/resources/images/mCont_ico2_on.png", // Active tab image |
|
247 |
- inactiveImage: "client/resources/images/mCont_ico2_off.png", |
|
246 |
+ activeImage: "client/resources/images/list_icon02_on.png", // Active tab image |
|
247 |
+ inactiveImage: "client/resources/images/list_icon02_off.png", |
|
248 | 248 |
}, |
249 | 249 |
], |
250 | 250 |
tabContents: [ |
--- client/views/pages/user/TotalSearch.vue
+++ client/views/pages/user/TotalSearch.vue
... | ... | @@ -13,7 +13,7 @@ |
13 | 13 |
</ul> |
14 | 14 |
</div> |
15 | 15 |
</div> |
16 |
- <div class="search-form mb-40"> |
|
16 |
+ <div class="search-form form "> |
|
17 | 17 |
<dl> |
18 | 18 |
<dd class="mb-15"> |
19 | 19 |
<p>기록유형</p> |
--- client/views/pages/user/BodoDetail.vue
+++ client/views/pages/user/VideoHistoryDetail.vue
... | ... | @@ -0,0 +1,149 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>영상 기록물</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li><img :src="homeicon" alt="Home Icon"> | |
8 | + <p>기록물</p> | |
9 | + </li> | |
10 | + <li><img :src="righticon" alt=""></li> | |
11 | + <li>영상 기록물</li> | |
12 | + </ul> | |
13 | + </div> | |
14 | + </div> | |
15 | + <form action="" class="gallery-form mb-40"> | |
16 | + <dl class="mb-20"> | |
17 | + <dd> | |
18 | + <p>영상 기록물 제목1 | |
19 | + </p> | |
20 | + <div class="date flex align-center"> | |
21 | + <img :src="calendaricon" alt=""> | |
22 | + <span>2025.02.28</span> | |
23 | + </div> | |
24 | + </dd> | |
25 | + | |
26 | + </dl> | |
27 | + <div class="gallery video"> | |
28 | + <img :src="eximg" alt=""> | |
29 | + </div> | |
30 | + </form> | |
31 | + | |
32 | + <h3>내용</h3> | |
33 | + <form action="" class=" info-form mb-50"> | |
34 | + <dl> | |
35 | + <dd> | |
36 | + <p> 대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 | |
37 | + 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어 있습니다.</p> | |
38 | + | |
39 | + </dd> | |
40 | + </dl> | |
41 | + </form> | |
42 | + | |
43 | + <h3>기본정보</h3> | |
44 | + <form action="" class="info-form mb-50"> | |
45 | + <dl> | |
46 | + <dd class="mb-20"> | |
47 | + <img :src="addressicon" alt=""> | |
48 | + <span>주소</span> | |
49 | + <p>경상북도 구미시 송정대로 55</p> | |
50 | + </dd> | |
51 | + <dd class="mb-20"> | |
52 | + <img :src="yearicon" alt=""> | |
53 | + <span>생산연도</span> | |
54 | + <p>2017</p> | |
55 | + | |
56 | + </dd> | |
57 | + <dd> | |
58 | + <img :src="categoryicon" alt=""> | |
59 | + <span>카테고리</span> | |
60 | + <ul class="category"> | |
61 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
62 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
63 | + </ul> | |
64 | + | |
65 | + </dd> | |
66 | + | |
67 | + </dl> | |
68 | + </form> | |
69 | + <div class="btn-group flex-center"> | |
70 | + <button class="red-line " type="button" @click="fnDeleteUser">삭제</button> | |
71 | + <button class="blue-line " type="button" @click="fnUpdateUser">수정</button> | |
72 | + <button class="gray-line-bg " type="button" @click="fnUpdateUser">목록</button> | |
73 | + <button class="gradient ">다운로드</button> | |
74 | + </div> | |
75 | + </div> | |
76 | +</template> | |
77 | + | |
78 | +<script> | |
79 | +import axios from "axios"; | |
80 | +import { ref } from 'vue'; | |
81 | +import { updateUsers, logOutProc, updatePassword } from "../../../resources/api/user" | |
82 | +// Import Swiper Vue components | |
83 | +import { CaretRightOutlined, PauseOutlined } from '@ant-design/icons-vue'; | |
84 | +import { Swiper, SwiperSlide } from 'swiper/vue'; | |
85 | + | |
86 | +// Import Swiper styles | |
87 | +import 'swiper/css'; | |
88 | + | |
89 | +import 'swiper/css/free-mode'; | |
90 | +import 'swiper/css/navigation'; | |
91 | +import 'swiper/css/thumbs'; | |
92 | + | |
93 | +// import required modules | |
94 | +import { FreeMode, Navigation, Thumbs } from 'swiper/modules'; | |
95 | + | |
96 | +export default { | |
97 | + components: { | |
98 | + PauseOutlined, | |
99 | + CaretRightOutlined, | |
100 | + Swiper, | |
101 | + SwiperSlide, | |
102 | + }, | |
103 | + setup() { | |
104 | + const thumbsSwiper = ref(null); | |
105 | + | |
106 | + const setThumbsSwiper = (swiper) => { | |
107 | + thumbsSwiper.value = swiper; | |
108 | + }; | |
109 | + | |
110 | + return { | |
111 | + thumbsSwiper, | |
112 | + setThumbsSwiper, | |
113 | + modules: [FreeMode, Navigation, Thumbs], | |
114 | + }; | |
115 | + }, | |
116 | + data() { | |
117 | + return { | |
118 | + resultitem: { | |
119 | + category1: true, | |
120 | + category2: true, | |
121 | + }, | |
122 | + calendaricon: 'client/resources/images/icon/calendaricon.png', | |
123 | + homeicon: 'client/resources/images/icon/home.png', | |
124 | + erroricon: 'client/resources/images/icon/error.png', | |
125 | + righticon: 'client/resources/images/icon/right.png', | |
126 | + addressicon: 'client/resources/images/icon/addressicon.png', | |
127 | + yearicon: 'client/resources/images/icon/yearicon.png', | |
128 | + categoryicon: 'client/resources/images/icon/categoryicon.png', | |
129 | + eximg: 'client/resources/images/img8.png', | |
130 | + slides: [ | |
131 | + { img: 'client/resources/images/visual.png', alt: 'Slide 1' }, | |
132 | + { img: 'client/resources/images/visual.png', alt: 'Slide 2' }, | |
133 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
134 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
135 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
136 | + { img: 'client/resources/images/visual.png', alt: 'Slide 3' }, | |
137 | + // Add more slides as needed | |
138 | + ], | |
139 | + | |
140 | + }; | |
141 | + }, | |
142 | + methods: { | |
143 | + }, | |
144 | + watch: {}, | |
145 | + computed: { | |
146 | + }, | |
147 | + mounted() { }, | |
148 | +}; | |
149 | +</script>(파일 끝에 줄바꿈 문자 없음) |
+++ client/views/pages/user/VideoHistoryInsert.vue
... | ... | @@ -0,0 +1,212 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>영상 기록물</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <li><img :src="homeicon" alt="Home Icon"> | |
8 | + <p>기록물</p> | |
9 | + </li> | |
10 | + <li><img :src="righticon" alt=""></li> | |
11 | + <li>영상 기록물</li> | |
12 | + </ul> | |
13 | + </div> | |
14 | + </div> | |
15 | + <form action="" class="insert-form mb-50"> | |
16 | + <dl> | |
17 | + <dd> | |
18 | + <label for="id" class="require">제목</label> | |
19 | + <div class="wfull"><input type="text" id="id" placeholder="제목을 입력하세요."></div> | |
20 | + </dd> | |
21 | + <div class="hr"></div> | |
22 | + <dd> | |
23 | + <label for="year">생산연도</label> | |
24 | + <input type="text" id="year" placeholder="생산연도를 입력하세요"> | |
25 | + </dd> | |
26 | + <div class="hr"></div> | |
27 | + <dd> | |
28 | + <label for="address">주소</label> | |
29 | + <div class="wfull"><input type="text" id="address" placeholder="주소를 입력하세요"></div> | |
30 | + </dd> | |
31 | + <div class="hr"></div> | |
32 | + <dd> | |
33 | + <label for="text">내용</label> | |
34 | + <div class="wfull"> | |
35 | + <EditorComponent :contents="insertDTO.cn" /> | |
36 | + </div> | |
37 | + </dd> | |
38 | + <div class="hr"></div> | |
39 | + <dd> | |
40 | + <label for="category" class="flex align-center"> | |
41 | + <p>카테고리</p><button type="button" class="category-add" @click="openModal">추가하기</button> | |
42 | + </label> | |
43 | + <ul class="category"> | |
44 | + <li v-for="(category, index) in selectedCtgries" :key="index"> {{ category }} <button type="button" class="cancel" @click="removeCategory(index)"><b>✕</b></button> | |
45 | + </li> | |
46 | + </ul> | |
47 | + </dd> | |
48 | + <div class="hr"></div> | |
49 | + <dd> | |
50 | + <label for="file" class="require">파일</label> | |
51 | + <ul class="wfull"> | |
52 | + <li class="flex align-center"> | |
53 | + <p>파일첨부</p> | |
54 | + <div class="invalid-feedback"><img :src="erroricon" alt=""><span>첨부파일은 10건까지 등록 가능하며, 건당 최대 100MB를 초과할 수 없습니다.</span></div> | |
55 | + </li> | |
56 | + <li class="file-insert"> | |
57 | + <input type="file" id="fileInput" class="file-input" multiple @change="showFileNames"> | |
58 | + <label for="fileInput" class="file-label mb-20"> | |
59 | + <div class="flex-center align-center"><img :src="fileicon" alt=""> | |
60 | + <p>파일첨부하기</p> | |
61 | + </div> | |
62 | + <p>파일을 첨부하시려면 이 영역으로 파일을 끌고 오거나 클릭해주세요</p> | |
63 | + </label> | |
64 | + <p class="mb-10">파일목록</p> | |
65 | + <div id="fileNames" class="file-names"> | |
66 | + <span v-if="fileNames.length === 0">선택된 파일이 없습니다.</span> | |
67 | + <div v-for="(file, index) in fileNames" :key="index" class="flex-sp-bw mb-5 file-wrap"> | |
68 | + <div class="file-name"> | |
69 | + <!-- Corrected here: Use file.icon instead of fileicons.img --> | |
70 | + <img :src="file.icon" alt="fileicon"> | |
71 | + <p>{{ file.name }}</p> | |
72 | + </div> | |
73 | + <button type="button" class="cancel" @click="removeFile(index)"><b>✕</b></button> | |
74 | + </div> | |
75 | + </div> | |
76 | + </li> | |
77 | + </ul> | |
78 | + </dd> | |
79 | + </dl> | |
80 | + </form> | |
81 | + <div class="btn-group flex-center"> | |
82 | + <button class="cancel">취소</button> | |
83 | + <button class="register">등록</button> | |
84 | + </div> | |
85 | + </div> | |
86 | + <CategorySelectModal v-if="isModalOpen" :selectedCtgries="selectedCtgries" @toggleModal="fnToggleModal" /> | |
87 | +</template> | |
88 | +<script> | |
89 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
90 | +// COMPONENT | |
91 | +import EditorComponent from '../../component/EditorComponent.vue'; | |
92 | +import CategorySelectModal from '../../component/modal/CategorySelectModal.vue'; | |
93 | + | |
94 | +export default { | |
95 | + components: { | |
96 | + DoubleLeftOutlined, | |
97 | + LeftOutlined, | |
98 | + RightOutlined, | |
99 | + DoubleRightOutlined, | |
100 | + EditorComponent, CategorySelectModal, | |
101 | + }, | |
102 | + | |
103 | + data() { | |
104 | + return { | |
105 | + // Define the image sources | |
106 | + homeicon: 'client/resources/images/icon/home.png', | |
107 | + erroricon: 'client/resources/images/icon/error.png', | |
108 | + righticon: 'client/resources/images/icon/right.png', | |
109 | + fileicon: 'client/resources/images/icon/file.png', | |
110 | + searchicon: 'client/resources/images/icon/search.png', | |
111 | + | |
112 | + isModalOpen: false, | |
113 | + | |
114 | + items: [ | |
115 | + { id: 1, category: '카테고리 1', selected: false }, | |
116 | + { id: 2, category: '카테고리 2', selected: false }, | |
117 | + { id: 3, category: '카테고리 3', selected: false }, | |
118 | + ], | |
119 | + fileNames: [], | |
120 | + insertDTO: { | |
121 | + sj: null, //제목 | |
122 | + cn: null, //내용 | |
123 | + adres: null, // 주소 | |
124 | + prdctnYear: null, // 생산연도 | |
125 | + ty: 'P', // 타입 ( P: 사진, V: 영상 ) | |
126 | + multipartFiles: null, // 첨부파일 정보 | |
127 | + ctgryIds: null, // 카테고리 정보 | |
128 | + }, | |
129 | + | |
130 | + files: [], | |
131 | + selectedCtgries: [], // 카테고리 목록 | |
132 | + }; | |
133 | + }, | |
134 | + computed: { | |
135 | + filteredItems() { | |
136 | + // This could be modified to support filtering based on searchQuery | |
137 | + return this.items.filter(item => | |
138 | + item.category.includes(this.searchQuery) | |
139 | + ); | |
140 | + } | |
141 | + }, | |
142 | + created() { | |
143 | + }, | |
144 | + methods: { | |
145 | + registerCategories() { | |
146 | + // Add selected categories to the displayed list | |
147 | + this.selectedCtgries = this.items | |
148 | + .filter(item => item.selected) | |
149 | + .map(item => item.category); | |
150 | + this.closeModal(); // Close modal after registration | |
151 | + }, | |
152 | + removeCategory(index) { | |
153 | + // Remove category from the list | |
154 | + this.selectedCtgries.splice(index, 1); | |
155 | + }, | |
156 | + searchCategories() { | |
157 | + // You can implement search logic if needed | |
158 | + }, | |
159 | + nextPage() { | |
160 | + if (this.currentPage < this.totalPages) { | |
161 | + this.currentPage++; | |
162 | + } | |
163 | + }, | |
164 | + previousPage() { | |
165 | + if (this.currentPage > 1) { | |
166 | + this.currentPage--; | |
167 | + } | |
168 | + }, | |
169 | + showFileNames(event) { | |
170 | + const files = event.target.files; | |
171 | + this.fileNames = []; // Clear previous file names | |
172 | + | |
173 | + for (let i = 0; i < files.length; i++) { | |
174 | + const file = files[i]; | |
175 | + const fileType = file.name.split('.').pop().toLowerCase(); // Get file extension | |
176 | + | |
177 | + // Set default icon | |
178 | + let iconPath = this.fileicons; | |
179 | + | |
180 | + // Determine the icon based on file type | |
181 | + if (['jpg', 'jpeg', 'png', 'gif'].includes(fileType)) { | |
182 | + iconPath = 'client/resources/images/icon/imgicon.png'; // Example for image files | |
183 | + } else if (['pdf'].includes(fileType)) { | |
184 | + iconPath = 'client/resources/images/icon/pdficon.png'; // Example for PDF files | |
185 | + } else if (['xls'].includes(fileType)) { | |
186 | + iconPath = 'client/resources/images/icon/excelicon.png'; // Example for audio files | |
187 | + } else if (['hwp'].includes(fileType)) { | |
188 | + iconPath = 'client/resources/images/icon/hwpicon.png'; // Example for video files | |
189 | + } | |
190 | + | |
191 | + // Push the file name and corresponding icon to the fileNames array | |
192 | + this.fileNames.push({ | |
193 | + name: file.name, | |
194 | + icon: iconPath | |
195 | + }); | |
196 | + } | |
197 | + }, | |
198 | + removeFile(index) { | |
199 | + // Remove file from the list | |
200 | + this.fileNames.splice(index, 1); | |
201 | + console.log(removeFile) | |
202 | + }, | |
203 | + openModal() { | |
204 | + this.isModalOpen = true; | |
205 | + }, | |
206 | + // 모달 닫기 | |
207 | + closeModal() { | |
208 | + this.isModalOpen = false; | |
209 | + }, | |
210 | + } | |
211 | +}; | |
212 | +</script> |
+++ client/views/pages/user/VideoHistorySearch.vue
... | ... | @@ -0,0 +1,414 @@ |
1 | +<template> | |
2 | + <div class="content"> | |
3 | + <div class="sub-title-area mb-30"> | |
4 | + <h2>영상 기록물</h2> | |
5 | + <div class="breadcrumb-list"> | |
6 | + <ul> | |
7 | + <!-- Bind the image source dynamically for homeicon --> | |
8 | + <li><img :src="homeicon" alt="Home Icon"> | |
9 | + <p>기록물</p> | |
10 | + </li> | |
11 | + <li><img :src="righticon" alt=""></li> | |
12 | + <li>영상 기록물</li> | |
13 | + </ul> | |
14 | + </div> | |
15 | + </div> | |
16 | + <div action="search" class="search-form form "> | |
17 | + <dl> | |
18 | + <dd class="mb-15"> | |
19 | + <p>검색범위</p> | |
20 | + <ul> | |
21 | + <li> | |
22 | + <input type="checkbox" id="allScope" v-model="isChkAllScope" | |
23 | + @change="fnChkAllOptions('scope')" /> | |
24 | + <label for="allScope">전체</label> | |
25 | + </li> | |
26 | + <li v-for="(scope, idx) in searchType" :key="idx"> | |
27 | + <input type="checkbox" :id="idx" :name="searchType" :value="scope.key" | |
28 | + v-model="searchReqDTO.searchType" @change="fnChkOption('scope')" /> | |
29 | + <label :for="idx">{{ scope.value }}</label> | |
30 | + </li> | |
31 | + </ul> | |
32 | + </dd> | |
33 | + <dd class="mb-15"> | |
34 | + <p>검색어</p> | |
35 | + <div class="wfull"><input type="text" v-model="searchReqDTO.searchText"></div> | |
36 | + </dd> | |
37 | + <dd class="mb-15"> | |
38 | + <p>생산연도</p> | |
39 | + <input type="date" v-model="searchReqDTO.startYear"> | |
40 | + <p class="mark">~</p> | |
41 | + <input type="date" v-model="searchReqDTO.endYear"> | |
42 | + </dd> | |
43 | + <dd class="mb-20"> | |
44 | + <p>카테고리</p> | |
45 | + <ul> | |
46 | + <li v-for="(category, idx) of categorys" :key="idx"> | |
47 | + <input type="checkbox" :id="category.ctgryId" name="categorys" :value="category.ctgryId" | |
48 | + v-model="searchReqDTO.searchCtgry" /> | |
49 | + <label :for="category.ctgryId">{{ category.ctgryNm }}</label> | |
50 | + </li> | |
51 | + </ul> | |
52 | + </dd> | |
53 | + <dd class="mb-15"> | |
54 | + <p>정렬</p> | |
55 | + <ul> | |
56 | + <li v-for="(order, idx) of orders" :key="idx"> | |
57 | + <input type="radio" :id="order.key" name="orders" :value="order.key" | |
58 | + v-model="searchReqDTO.order" /> | |
59 | + <label :for="order.key">{{ order.value }}</label> | |
60 | + </li> | |
61 | + </ul> | |
62 | + </dd> | |
63 | + <div class="btn-group"> | |
64 | + <button class="reset"><img :src="reseticon" alt=""> | |
65 | + <p>초기화</p> | |
66 | + </button> | |
67 | + <button class="search"><img :src="searchicon" alt=""> | |
68 | + <p>검색</p> | |
69 | + </button> | |
70 | + </div> | |
71 | + | |
72 | + </dl> | |
73 | + | |
74 | + </div> | |
75 | + <div class="search-result"> | |
76 | + <div class="tabs"> | |
77 | + <div class="flex-sp-bw mb-20 align-center"> | |
78 | + <div class="resultext "> | |
79 | + <img :src="resulticon" alt=""> | |
80 | + <p>총 <b>{{ count }}개</b>의 영상 기록물이 검색되었습니다. </p> | |
81 | + </div> | |
82 | + <div class="flex "> | |
83 | + <ul class="tab-box mb-20"> | |
84 | + <li v-for="(tab, index) in tabs" :key="index" class="tab-title" | |
85 | + :class="{ active: selectedTab === tab.id }" @click="selectTab(tab.id)"> | |
86 | + <img :src="selectedTab === tab.id ? tab.activeImage : tab.inactiveImage" | |
87 | + :alt="tab.title" class="tab-icon" /> | |
88 | + <p><b>{{ tab.title }}</b></p> | |
89 | + </li> | |
90 | + </ul> | |
91 | + <div class="select-box"> | |
92 | + <select v-model="itemsPerPage" @change="changeItemsPerPage"> | |
93 | + <option :value="5" selected>5개</option> | |
94 | + <option :value="10">10개</option> | |
95 | + <option :value="15">15개</option> | |
96 | + </select> | |
97 | + </div> | |
98 | + </div> | |
99 | + | |
100 | + </div> | |
101 | + | |
102 | + <div class="tab-content"> | |
103 | + <!-- Loop through tabContents, and only display content that matches selectedTab --> | |
104 | + <div v-for="(tabContent, idx) in tabContents" :key="idx"> | |
105 | + <!-- Display content only if the tab's ID matches the selectedTab --> | |
106 | + <div v-show="tabContent.id === selectedTab"> | |
107 | + <!-- 카드형 Section (Card Layout) --> | |
108 | + <div v-if="tabContent.viewType === 'card'"> | |
109 | + <ul class="card-wrap"> | |
110 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
111 | + <div class="result-box"> | |
112 | + <!-- Main Image Section --> | |
113 | + <div class="main-img"> | |
114 | + <img :src="resultitem.img" alt="" class="tab-image" /> | |
115 | + </div> | |
116 | + <!-- Text Section --> | |
117 | + <div class="text-box"> | |
118 | + <router-link :to="{ path: '/VideoHistoryDetail.page' }"> | |
119 | + <h5>{{ resultitem.title }}</h5> | |
120 | + </router-link> | |
121 | + | |
122 | + <p class="address">{{ resultitem.address }}</p> | |
123 | + <p class="text">{{ resultitem.content }}</p> | |
124 | + | |
125 | + <div class="mb-20"> | |
126 | + <ul class="category"> | |
127 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
128 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
129 | + </ul> | |
130 | + </div> | |
131 | + | |
132 | + <div class="date"> | |
133 | + <ul> | |
134 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
135 | + <li>|</li> | |
136 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
137 | + </ul> | |
138 | + </div> | |
139 | + </div> | |
140 | + </div> | |
141 | + </li> | |
142 | + </ul> | |
143 | + | |
144 | + <!-- Empty State if no results in paginatedItems --> | |
145 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
146 | + <p>등록된 게시물이 없습니다.</p> | |
147 | + </div> | |
148 | + </div> | |
149 | + | |
150 | + <!-- 리스트형 Section (List Layout) --> | |
151 | + <div v-if="tabContent.viewType === 'list'"> | |
152 | + <ul class="list-wrap"> | |
153 | + <li v-for="(resultitem, index) in paginatedItems" :key="index" class="mb-30"> | |
154 | + <div class="text-box"> | |
155 | + <router-link :to="{ path: '/VideoHistoryDetail.page' }"> | |
156 | + <h5>{{ resultitem.title }}</h5> | |
157 | + </router-link> | |
158 | + <p class="address">{{ resultitem.address }}</p> | |
159 | + | |
160 | + <div class="flex-sp-bw"> | |
161 | + <div class="mb-20"> | |
162 | + <ul class="category"> | |
163 | + <li v-if="resultitem.category1" class="category1">카테고리1</li> | |
164 | + <li v-if="resultitem.category2" class="category2">카테고리2</li> | |
165 | + </ul> | |
166 | + </div> | |
167 | + | |
168 | + <div class="date "> | |
169 | + <ul> | |
170 | + <li>생산연도 <b>{{ resultitem.year }}</b></li> | |
171 | + <li>|</li> | |
172 | + <li>등록 <b>{{ resultitem.date }}</b></li> | |
173 | + </ul> | |
174 | + </div> | |
175 | + </div> | |
176 | + </div> | |
177 | + </li> | |
178 | + </ul> | |
179 | + | |
180 | + <!-- Empty State if no results in paginatedItems --> | |
181 | + <div v-if="paginatedItems.length === 0" class="no-results"> | |
182 | + <p>등록된 게시물이 없습니다.</p> | |
183 | + </div> | |
184 | + </div> | |
185 | + </div> | |
186 | + </div> | |
187 | + </div> | |
188 | + </div> | |
189 | + | |
190 | + <div class="btn-group flex-end mt-40"><button class="register"> <router-link | |
191 | + :to="{ path: '/VideoHistoryInsert.page' }">등록</router-link></button></div> | |
192 | + <div class="pagination flex-center mt-40"> | |
193 | + | |
194 | + <!-- Previous and Next Page Buttons --> | |
195 | + <button> | |
196 | + <DoubleLeftOutlined /> | |
197 | + </button> | |
198 | + <button @click="previousPage" :disabled="currentPage === 1"> | |
199 | + <LeftOutlined /> | |
200 | + </button> | |
201 | + <button class="page-number clicked">1</button> | |
202 | + <button @click="nextPage" :disabled="currentPage === totalPages"> | |
203 | + <RightOutlined /> | |
204 | + </button> | |
205 | + <button> | |
206 | + <DoubleRightOutlined /> | |
207 | + </button> | |
208 | + </div> | |
209 | + </div> | |
210 | + </div> | |
211 | + | |
212 | +</template> | |
213 | +<script> | |
214 | +import { DoubleLeftOutlined, LeftOutlined, RightOutlined, DoubleRightOutlined } from '@ant-design/icons-vue'; | |
215 | +import { findAllCategoryProc } from "../../../resources/api/category"; // 카테고리 목록 검색 | |
216 | + | |
217 | +export default { | |
218 | + components: { | |
219 | + DoubleLeftOutlined, | |
220 | + LeftOutlined, | |
221 | + RightOutlined, | |
222 | + DoubleRightOutlined, | |
223 | + }, | |
224 | + data() { | |
225 | + return { | |
226 | + selectedTab: 1, | |
227 | + // 검색용 객체 | |
228 | + searchReqDTO: { | |
229 | + searchType: [], | |
230 | + searchText: null, | |
231 | + startYear: null, | |
232 | + endYear: null, | |
233 | + searchTy: null, | |
234 | + searchCtgry: [], | |
235 | + order: "rgsde", | |
236 | + }, | |
237 | + tabs: [ | |
238 | + | |
239 | + { | |
240 | + id: 1, | |
241 | + title: "카드형", | |
242 | + activeImage: "client/resources/images/list_icon01_on.png", // Active tab image | |
243 | + inactiveImage: "client/resources/images/list_icon01_off.png", | |
244 | + }, | |
245 | + { | |
246 | + id: 2, | |
247 | + title: "리스트형", | |
248 | + activeImage: "client/resources/images/list_icon02_on.png", // Active tab image | |
249 | + inactiveImage: "client/resources/images/list_icon02_off.png", | |
250 | + }, | |
251 | + ], | |
252 | + tabContents: [ | |
253 | + { id: 1, viewType: 'card', list: [{ sj: 'Item 1', rgsde: '2025-03-01', files: [{ filePath: 'image1.png' }] }] }, | |
254 | + { id: 2, viewType: 'list', list: [{ sj: 'Item 2', rgsde: '2025-03-02', files: [{ filePath: 'image2.png' }] }] }, | |
255 | + ], | |
256 | + paginatedItems: [], | |
257 | + resultitems: [ | |
258 | + { | |
259 | + img: 'client/resources/images/img6.png', | |
260 | + title: '영상 기록물 제목', | |
261 | + address: '경상북도 구미시 송정대로 55', | |
262 | + content: '대한민국 최대의 내륙 산업단지를 보유하고, 서울로부터 277km, 부산으로부터 167km 거리에 있으며, 면적은 615㎢로 경상북도 전체 면적의 3.2%에 달합니다. 인구는 41만 명이고, 선산읍, 고아읍, 산동읍을 비롯한 3읍, 5면, 17개 동으로 구성되어…', | |
263 | + category1: true, | |
264 | + category2: true, | |
265 | + year: 2020, | |
266 | + date: '2021-01-01' | |
267 | + }, | |
268 | + | |
269 | + ], | |
270 | + currentPage: 1, // Current page number | |
271 | + itemsPerPage: 5, | |
272 | + resulticon: "client/resources/images/icon/r-check.png", | |
273 | + homeicon: 'client/resources/images/icon/home.png', | |
274 | + searchicon: 'client/resources/images/icon/search.png', | |
275 | + reseticon: 'client/resources/images/icon/reset.png', | |
276 | + righticon: 'client/resources/images/icon/right.png', | |
277 | + count: 23, | |
278 | + checkOptions: [ | |
279 | + '전체', | |
280 | + '사진', | |
281 | + '영상', | |
282 | + '미디어 영상', | |
283 | + '보도자료', | |
284 | + ], | |
285 | + checkOptions2: [ | |
286 | + '전체', | |
287 | + '제목', | |
288 | + '내용', | |
289 | + '주소', | |
290 | + ], | |
291 | + checkOptions3: [ | |
292 | + '카테고리1', | |
293 | + '카테고리2', | |
294 | + '카테고리3', | |
295 | + '카테고리4', | |
296 | + '카테고리5', | |
297 | + ], | |
298 | + checkOptions4: [ | |
299 | + '최신', | |
300 | + '인기', | |
301 | + ], | |
302 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
303 | + searchType: [ | |
304 | + { key: "sj", value: "제목" }, | |
305 | + { key: "cn", value: "내용" }, | |
306 | + { key: "adres", value: "주소" }, | |
307 | + ], // 검색범위 목록 | |
308 | + categorys: [], // 카테고리 목록 | |
309 | + orders: [ | |
310 | + { key: "rgsde", value: "최신" }, | |
311 | + { key: "rdcnt", value: "인기" }, | |
312 | + ], // 정렬 목록 | |
313 | + }; | |
314 | + }, | |
315 | + computed: { | |
316 | + // Total number of pages | |
317 | + totalPages() { | |
318 | + return Math.ceil(this.resultitems.length / this.itemsPerPage); | |
319 | + }, | |
320 | + | |
321 | + // Paginated items based on current page and items per page | |
322 | + paginatedItems() { | |
323 | + const start = (this.currentPage - 1) * this.itemsPerPage; | |
324 | + const end = start + this.itemsPerPage; | |
325 | + return this.resultitems.slice(start, end); | |
326 | + }, | |
327 | + }, | |
328 | + created() { | |
329 | + // 초기 데이터 세팅 | |
330 | + this.isChkAllScope = true; | |
331 | + this.searchReqDTO.searchType = this.searchType.map(item => item.key); | |
332 | + this.searchReqDTO.order = this.orders[0].key | |
333 | + | |
334 | + this.fnFindCategorys(); // 카테고리 목록 조회 (검색조건 없음) | |
335 | + }, | |
336 | + methods: { | |
337 | + selectTab(tabId) { | |
338 | + this.selectedTab = tabId; // Update the selected tab index | |
339 | + }, | |
340 | + // Change the number of items displayed per page | |
341 | + changeItemsPerPage() { | |
342 | + this.currentPage = 1; // Reset to first page when changing items per page | |
343 | + }, | |
344 | + previousPage() { | |
345 | + if (this.currentPage > 1) { | |
346 | + this.currentPage--; | |
347 | + } | |
348 | + }, | |
349 | + | |
350 | + // Go to the next page | |
351 | + nextPage() { | |
352 | + if (this.currentPage < this.totalPages) { | |
353 | + this.currentPage++; | |
354 | + } | |
355 | + }, | |
356 | + isChkAllScope: false, // 검색범위 전체 체크 여부 | |
357 | + searchType: [ | |
358 | + { key: "sj", value: "제목" }, | |
359 | + { key: "cn", value: "내용" }, | |
360 | + { key: "adres", value: "주소" }, | |
361 | + ], // 검색범위 목록 | |
362 | + categorys: [], // 카테고리 목록 | |
363 | + orders: [ | |
364 | + { key: "rgsde", value: "최신" }, | |
365 | + { key: "rdcnt", value: "인기" }, | |
366 | + ], // 정렬 목록 | |
367 | + | |
368 | + async fnFindCategorys() { | |
369 | + try { | |
370 | + const response = await findAllCategoryProc(); | |
371 | + this.categorys = response.data.data.ctgry; | |
372 | + } catch (error) { | |
373 | + if (error.response) { | |
374 | + console.log("에러 응답:", error.response.data); | |
375 | + } | |
376 | + console.error("Error:", error); | |
377 | + } | |
378 | + }, | |
379 | + | |
380 | + // 통합검색 | |
381 | + async fnFindAllDatas() { | |
382 | + try { | |
383 | + let params = {}; | |
384 | + if (this.searchReqDTO.searchRecord.length > 0) { | |
385 | + params.searchRecords = this.searchReqDTO.searchRecord.join(','); | |
386 | + } | |
387 | + if (this.searchReqDTO.searchType.length > 0) { | |
388 | + params.searchTypes = this.searchReqDTO.searchType.join(','); | |
389 | + } | |
390 | + if (this.searchReqDTO.searchCtgry.length > 0) { | |
391 | + params.searchCtgries = this.searchReqDTO.searchCtgry.join(','); | |
392 | + } | |
393 | + params.searchText = this.searchReqDTO.searchText; | |
394 | + params.startYear = this.searchReqDTO.startYear; | |
395 | + params.endYear = this.searchReqDTO.endYear; | |
396 | + params.order = this.searchReqDTO.order; | |
397 | + | |
398 | + // API 호출 | |
399 | + const response = await findAllDatas(params); | |
400 | + this.searchResult = response.data.data.searchResult; | |
401 | + } catch (error) { | |
402 | + if (error.response) { | |
403 | + console.log("에러 응답:", error.response.data); | |
404 | + } | |
405 | + console.error("Error:", error); | |
406 | + } | |
407 | + }, | |
408 | + }, | |
409 | + | |
410 | + | |
411 | + | |
412 | +}; | |
413 | +</script> | |
414 | +<style scoped></style>(파일 끝에 줄바꿈 문자 없음) |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?