layout03User.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <template>
  2. <div class="core--component--wrap user--list">
  3. <div>
  4. <div class="inner--header--wrap">
  5. <h2 class="inner--component--title none--after">
  6. 가입자
  7. </h2>
  8. <span class="inner--component--date">{{ current }}</span>
  9. </div>
  10. <div class="inner--content">
  11. <div class="oper--stat">
  12. <div class="card--title">
  13. <h3>테넌트 현황</h3>
  14. </div>
  15. <div class="card--alarm mb--1rem">
  16. <div class="card tenant1">
  17. <div class="ico" />
  18. <div class="alarm--txt">
  19. <p>Total</p>
  20. <span>{{ objCount.total }}</span>
  21. </div>
  22. </div>
  23. <div class="card tenant2 gray--alarm">
  24. <div class="ico" />
  25. <div class="alarm--txt">
  26. <p>대내</p>
  27. <span>{{ objCount.customer0 }}</span>
  28. </div>
  29. </div>
  30. </div>
  31. <div class="card--alarm">
  32. <div class="card tenant3 gray--alarm">
  33. <div class="ico" />
  34. <div class="alarm--txt">
  35. <p>대외</p>
  36. <span>{{ objCount.customer1 }}</span>
  37. </div>
  38. </div>
  39. <div class="card tenant4 gray--alarm">
  40. <div class="ico" />
  41. <div class="alarm--txt">
  42. <p>공용</p>
  43. <span>{{ objCount.customer2 }}</span>
  44. </div>
  45. </div>
  46. </div>
  47. </div>
  48. <div class="oper--stat">
  49. <div class="card--title">
  50. <h3>라이선스 인증 현황</h3>
  51. </div>
  52. <div class="card--alarm gap--0">
  53. <div class="card license1">
  54. <div class="ico" />
  55. <div class="alarm--txt">
  56. <p>인증</p>
  57. <span>{{ objCount.licentVerified }}</span>
  58. </div>
  59. </div>
  60. <div class="card license2 no--alarm">
  61. <div class="ico" />
  62. <div class="alarm--txt">
  63. <p>미인증</p>
  64. <span>{{ objCount.licentNotVerified }}</span>
  65. </div>
  66. </div>
  67. </div>
  68. </div>
  69. </div>
  70. </div>
  71. <!-- 위젯 영역 -->
  72. <WidgetM
  73. v-if="isLoaded && widgetSize === 'M'"
  74. :items="userItems"
  75. :total-cnt="objCount.total"
  76. />
  77. <WidgetS
  78. v-if="isLoaded && widgetSize === 'S'"
  79. :items="userItems"
  80. :total-cnt="objCount.total"
  81. />
  82. </div>
  83. </template>
  84. <script setup>
  85. /***********************
  86. * import
  87. ************************/
  88. import { useI18n } from "vue-i18n"
  89. import apiUrl from '@/composables/useApi';
  90. import useAxios from '@/composables/useAxios';
  91. import useUtil from '@/composables/useUtil';
  92. import WidgetM from "@/components/home/dashboard/layout03/user/layout03UserWidgetM.vue"
  93. import WidgetS from '@/components/home/dashboard/layout03/user/layout03UserWidgetS.vue'
  94. import dayjs from "#build/dayjs.imports.mjs";
  95. import testJson from "../../test.json"
  96. /***********************
  97. * plugins inject
  98. ************************/
  99. const { $toast, $log, $dayjs, $eventBus } = useNuxtApp()
  100. // props
  101. const props = defineProps({
  102. config: {
  103. type: Object,
  104. default: () => {}
  105. },
  106. intervalTime: {
  107. type: Number,
  108. default: 5000
  109. }
  110. })
  111. // 참조가능 데이터 설정
  112. defineExpose({})
  113. // 발신 이벤트 선언
  114. const emit = defineEmits([""]);
  115. const i18n = useI18n();
  116. /***********************
  117. * data & created
  118. ************************/
  119. // TenantName
  120. const tenantName = computed(() => useAuthStore().getTenantName);
  121. // 설정된 위젯 사이즈 : M or S
  122. const widgetSize = computed(() => props.config?.widgetSize || 'M');
  123. // Swiper 표시 slide 갯수
  124. const slideItemSize = computed(() => widgetSize.value == 'M' ? 8 : 14)
  125. // 현재 일시
  126. const current = ref(dayjs().format('YYYY-MM-DD HH:mm:ss'));
  127. // 카운트 정보
  128. const objCount = ref({
  129. total: 0,
  130. customer0: 0,
  131. customer1: 0,
  132. customer2: 0,
  133. licentVerified: 0,
  134. licentNotVerified: 0,
  135. })
  136. // 가입자 리스트
  137. const userItems = ref([]);
  138. // 데이터 조회 여부
  139. const isLoaded = ref(false);
  140. /***********************
  141. * Methods
  142. ************************/
  143. const fnInit = () => {
  144. fnGetUserList();
  145. }
  146. const fnReset = () => {
  147. objCount.value.total = 0;
  148. objCount.value.customer0 = 0;
  149. objCount.value.customer1 = 0;
  150. objCount.value.customer2 = 0;
  151. objCount.value.licentVerified = 0;
  152. objCount.value.licentNotVerified = 0;
  153. current.value = dayjs().format('YYYY-MM-DD HH:mm:ss');
  154. }
  155. /** 가입자 목록 조회 */
  156. function fnGetUserList() {
  157. isLoaded.value = false;
  158. const params = {
  159. tenantName: tenantName.value,
  160. }
  161. useAxios().post(apiUrl.getUserList, params).then((res) => {
  162. const {resCode, resMsg, data} = res.data;
  163. if(resCode == 200) {
  164. // 테스트를 위한 테스트 코드 (삭제 필수)
  165. // fnParseData([...(data?.items || []), ...testJson.user.data.items])
  166. fnParseData(data.items || [])
  167. } else {
  168. $log.debug("[dashboard][fnGetUserList][error]", `[${resCode}] ${resMsg}`);
  169. }
  170. }).catch((error)=>{
  171. // 테스트를 위한 테스트 코드 (삭제 필수)
  172. // fnParseData([...testJson.user.data.items])
  173. $log.debug("[dashboard][fnGetUserList][error]", error)
  174. useErrorHandler().fnSetCommErrorHandle(error, fnGetUserList)
  175. // 테스트 데이터 파싱
  176. }).finally(()=>{
  177. $log.debug("[dashboard][fnGetUserList][finished]")
  178. isLoaded.value = true;
  179. })
  180. }
  181. /**
  182. * 데이터 가공
  183. */
  184. function fnParseData(items = []) {
  185. userItems.value = [];
  186. objCount.value.total = 0;
  187. objCount.value.customer0 = 0;
  188. objCount.value.customer1 = 0;
  189. objCount.value.customer2 = 0;
  190. objCount.value.licentVerified = 0;
  191. objCount.value.licentNotVerified = 0;
  192. let temp = [];
  193. // 정렬 적용
  194. temp = items.sort((a, b) => {
  195. const perA = fnGetPercentValue(a.maxSubscriber, a.curSubscriber);
  196. const perB = fnGetPercentValue(b.maxSubscriber, b.curSubscriber);
  197. return +perA < +perB ? 1 : (+perA > +perB ? -1 : 0);
  198. })
  199. // 데이터 가공
  200. temp = temp.map((item) => {
  201. //카운트 셋팅
  202. const { customerType, licenseKey } = item;
  203. objCount.value.total = objCount.value.total+1;
  204. if(customerType == 0) objCount.value.customer0 = objCount.value.customer0 + 1;
  205. else if(customerType == 1) objCount.value.customer1 = objCount.value.customer1 + 1;
  206. else if(customerType == 2) objCount.value.customer2 = objCount.value.customer2 + 1;
  207. if(licenseKey) objCount.value.licentVerified = objCount.value.licentVerified + 1;
  208. else objCount.value.licentNotVerified = objCount.value.licentNotVerified + 1;
  209. //percent
  210. const { maxSubscriber, curSubscriber } = item;
  211. const perSubscriber = fnGetPercentValue(maxSubscriber, curSubscriber);
  212. //심각도
  213. const level = fnGetLevel(perSubscriber);
  214. //차트 데이터
  215. const chartData = {
  216. datasets: [
  217. {
  218. data: [curSubscriber , maxSubscriber - curSubscriber],
  219. backgroundColor: fnGetChartColorSet(level),
  220. borderWidth: 0,
  221. }
  222. ]
  223. }
  224. return {
  225. ...item,
  226. level,
  227. perSubscriber,
  228. chartData,
  229. }
  230. });
  231. userItems.value = splitIntoChunk(temp, slideItemSize.value)
  232. }
  233. /** Array Spliter */
  234. function splitIntoChunk(arr, chunk) {
  235. // 빈 배열 생성
  236. const result = [];
  237. for (let index=0; index < arr.length; index += chunk) {
  238. // slice() 메서드를 사용하여 특정 길이만큼 배열을 분리함
  239. const tempArray = arr.slice(index, index + chunk);
  240. // 빈 배열에 특정 길이만큼 분리된 배열을 추가
  241. result.push(tempArray);
  242. }
  243. return result;
  244. }
  245. /** make percent data */
  246. const fnGetPercentValue = (max, curr) => {
  247. return useUtil.toRoundFix((curr / max) * 100, 0);
  248. }
  249. const fnGetLevel = (per) => {
  250. if(per >= 95) return 'critical'; //critical
  251. else if(per >= 90) return 'major'; //major
  252. else if(per >= 85) return 'minor'; //minor
  253. else return ''; //normal
  254. }
  255. /** 차트 데이터 > 컬러셋 */
  256. const fnGetChartColorSet = (level) => {
  257. if(level == 'critical') return ['#f00','#EAEAEA']; //critical
  258. else if(level == 'major') return ['#C96103','#EAEAEA']; //major
  259. else if(level == 'minor') return ['#DDA405','#EAEAEA']; //minor
  260. else return ['#2D8CFA','#EAEAEA']; //normal
  261. }
  262. onMounted(() => fnInit());
  263. // 위젯사이즈 변경 watch
  264. watch(() => widgetSize.value, (v) => {
  265. fnReset();
  266. fnInit();
  267. }, {deep: true})
  268. </script>