1/* 2 * Copyright (c) 2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import bundle from '@ohos.bundle'; 17import router from '@ohos.router'; 18import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; 19import privacyManager from '@ohos.privacyManager' 20import { backBar } from "../common/components/backBar"; 21import Constants from '../common/utils/constant'; 22import { noNeedDisplayApp, userGrantPermissions, permissionGroupIds } from "../common/model/permissionGroup"; 23import { getPermissionGroup } from '../common/utils/utils' 24 25var TAG = 'PermissionManager_MainAbility:' 26 27@Extend(Image) function customizeImage(width: number, height: number) { 28 .objectFit(ImageFit.Contain) 29 .width(width) 30 .height(height) 31}; 32 33class StringObj { 34 visits: string 35 recent_visit: string 36 morning: string 37 afternoon: string 38 constructor(visits: string, recent_visit: string, morning: string, afternoon: string) { 39 this.visits = visits 40 this.recent_visit = recent_visit 41 this.morning = morning 42 this.afternoon = afternoon 43 } 44} 45 46@Entry 47@Component 48struct permissionRecordPage { 49 @State groups: any[] = [] 50 @State applicationInfos: any[] = [] 51 @State permissionApplications: any[] = [] 52 @State permissionIndex: number = -1 53 @State applicationIndex: number = -1 54 @State strings: StringObj = new StringObj('', '', '', '') 55 @State currentIndex: number = 0 56 @Builder TabBuilder(index: number) { 57 Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 58 Text(index ? $r('app.string.application') : $r('app.string.authority')) 59 .fontColor(this.currentIndex == index ? $r('app.color.button_color') : $r('app.color.label_color')) 60 .fontWeight(this.currentIndex == index ? FontWeight.Bold : FontWeight.Regular) 61 .lineHeight(Constants.TEXT_LINE_HEIGHT) 62 if(this.currentIndex == index) { 63 Row().width(Constants.FULL_WIDTH).height(Constants.TAB_DECORATION_HEIGHT) 64 .backgroundColor($r('app.color.button_color')) 65 .position({ y: Constants.TAB_DECORATION_POSITION_Y }) 66 } 67 }.height(Constants.TAB_HEIGHT) 68 } 69 70 @Builder ListItemLayout(item, index, dimension) { 71 ListItem() { 72 Column() { 73 Column() { 74 Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { 75 Row() { 76 if(dimension) { 77 Image(item.icon) 78 .customizeImage(Constants.MANAGEMENT_IMAGE_WIDTH, Constants.MANAGEMENT_IMAGE_HEIGHT) 79 .margin({ right: Constants.MANAGEMENT_IMAGE_MARGIN_RIGHT_RECORD, left: Constants.MANAGEMENT_IMAGE_MARGIN_LEFT }) 80 }else { 81 Image(item.icon) 82 .customizeImage(Constants.APPLICATION_IMAGE_WIDTH, Constants.APPLICATION_IMAGE_HEIGHT) 83 .margin({ right: Constants.MANAGEMENT_IMAGE_MARGIN_RIGHT }) 84 } 85 Column() { 86 Text(item.groupName) 87 .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE) 88 .fontWeight(FontWeight.Medium) 89 .fontColor($r('app.color.label_color')) 90 .lineHeight(Constants.TEXT_LINE_HEIGHT) 91 .margin({ bottom: Constants.TERTIARY_LABEL_MARGIN_BOTTOM }) 92 if(dimension) { 93 Text(this.strings.visits + item.sum) 94 .fontSize(Constants.TEXT_SMAL_FONT_SIZE) 95 .fontColor($r('app.color.label_color_light')) 96 .lineHeight(Constants.TEXT_SMALL_LINE_HEIGHT) 97 }else { 98 Row() { 99 if (item.permissions) { 100 ForEach(item.permissions, permission => { 101 Image(permission.icon) 102 .customizeImage(Constants.IMAGE_WIDTH_RECORD_APPLICATION, Constants.IMAGE_HEIGHT_RECORD_APPLICATION) 103 .margin({ right: Constants.APPLICATION_TEXT_MARGIN_RIGHT }) 104 }) 105 } 106 } 107 } 108 }.flexGrow(Constants.FLEX_GROW) 109 .alignItems(HorizontalAlign.Start) 110 if(dimension) { 111 if(index == this.permissionIndex) { 112 Image($r('app.media.xiangshangjiantou')) 113 .customizeImage(Constants.IMAGE_WIDTH_RECORD, Constants.IMAGE_HEIGHT_RECORD) 114 }else { 115 Image($r('app.media.xiangxiajiantou')) 116 .customizeImage(Constants.IMAGE_WIDTH_RECORD, Constants.IMAGE_HEIGHT_RECORD) 117 } 118 }else { 119 if(index == this.applicationIndex) { 120 Image($r('app.media.xiangshangjiantou')) 121 .customizeImage(Constants.IMAGE_WIDTH_RECORD, Constants.IMAGE_HEIGHT_RECORD) 122 }else { 123 Image($r('app.media.xiangxiajiantou')) 124 .customizeImage(Constants.IMAGE_WIDTH_RECORD, Constants.IMAGE_HEIGHT_RECORD) 125 } 126 } 127 } 128 .width(Constants.FULL_WIDTH) 129 .height(dimension ? Constants.LISTITEM_HEIGHT_PERMISSION : Constants.LISTITEM_HEIGHT_APPLICATION) 130 } 131 }.onClick(() => { 132 dimension ? 133 (this.permissionIndex = this.permissionIndex == index ? -1 : index) : 134 (this.applicationIndex = this.applicationIndex == index ? -1 : index) 135 if(dimension) { 136 this.permissionApplications = this.applicationInfos.filter(appInfo => { 137 return appInfo.groupNames.includes(item.groupName) 138 }) 139 } 140 }) 141 if(dimension && (index == this.permissionIndex)) { 142 List() { 143 ForEach(this.permissionApplications, (permissionApplication) => { 144 ListItem() { 145 Row() { 146 Image(permissionApplication.icon) 147 .customizeImage(Constants.APPLICATION_IMAGE_WIDTH, Constants.APPLICATION_IMAGE_HEIGHT) 148 .margin({ right: Constants.MANAGEMENT_IMAGE_MARGIN_RIGHT }) 149 Column() { 150 Row().width(Constants.FULL_WIDTH).height(Constants.TEXT_DECORATION_HEIGHT) 151 .backgroundColor($r("app.color.label_color_lightest")) 152 .margin({ bottom: Constants.LISTITEM_MARGIN_BOTTOM_PERMISSION }) 153 Text(permissionApplication.groupName) 154 .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE) 155 .fontWeight(FontWeight.Medium) 156 .fontColor($r('app.color.label_color')) 157 .lineHeight(Constants.TEXT_LINE_HEIGHT) 158 .margin({ bottom: Constants.TERTIARY_LABEL_MARGIN_BOTTOM }) 159 Text(this.strings.visits + this.getAppRecords(permissionApplication, item.groupName, true) + 160 this.strings.recent_visit + this.getTime(this.getAppRecords(permissionApplication, item.groupName, false))) 161 .fontSize(Constants.TEXT_SMAL_FONT_SIZE) 162 .fontColor($r('app.color.label_color_light')) 163 .lineHeight(Constants.TEXT_SMALL_LINE_HEIGHT) 164 }.alignItems(HorizontalAlign.Start) 165 .height(Constants.FULL_HEIGHT) 166 } 167 }.height(Constants.LISTITEM_HEIGHT_APPLICATION) 168 .onClick(() => { 169 globalThis.applicationInfo = { 170 'bundleName': permissionApplication.name, 171 'api': permissionApplication.api, 172 'tokenId': permissionApplication.accessTokenId, 173 'iconId': permissionApplication.icon, 174 'labelId': permissionApplication.groupName, 175 'permissions': permissionApplication.reqUserPermissions, 176 'groupId': permissionApplication.groupIds 177 } 178 router.pushUrl({ url: 'pages/application-secondary' }); 179 }) 180 }) 181 } 182 } 183 if(!dimension && (index == this.applicationIndex)) { 184 List() { 185 ForEach(item.permissions, (permission) => { 186 ListItem() { 187 Row() { 188 Image(permission.icon) 189 .customizeImage(Constants.MANAGEMENT_IMAGE_WIDTH, Constants.MANAGEMENT_IMAGE_HEIGHT) 190 .margin({ right: Constants.MANAGEMENT_IMAGE_MARGIN_RIGHT_RECORD, left: Constants.MANAGEMENT_IMAGE_MARGIN_LEFT }) 191 Column() { 192 Row().width(Constants.FULL_WIDTH).height(Constants.TEXT_DECORATION_HEIGHT) 193 .backgroundColor($r("app.color.label_color_lightest")) 194 .margin({ bottom: Constants.LISTITEM_MARGIN_BOTTOM_APPLICATION }) 195 Text(permission.groupName) 196 .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE) 197 .fontWeight(FontWeight.Medium) 198 .fontColor($r('app.color.label_color')) 199 .lineHeight(Constants.TEXT_LINE_HEIGHT) 200 .margin({ bottom: Constants.TERTIARY_LABEL_MARGIN_BOTTOM }) 201 Text(this.strings.visits + permission['count' + item.accessTokenId] 202 + this.strings.recent_visit + this.getTime(permission['lastTime' + item.accessTokenId])) 203 .fontSize(Constants.TEXT_SMAL_FONT_SIZE) 204 .fontColor($r('app.color.label_color_light')) 205 .lineHeight(Constants.TEXT_SMALL_LINE_HEIGHT) 206 }.alignItems(HorizontalAlign.Start) 207 .height(Constants.FULL_HEIGHT) 208 } 209 }.height(Constants.LISTITEM_HEIGHT_PERMISSION) 210 .onClick(() => { 211 globalThis.applicationInfo = { 212 'bundleName': item.name, 213 'api': item.api, 214 'tokenId': item.accessTokenId, 215 'iconId': item.icon, 216 'labelId': item.groupName, 217 'permissions': item.reqUserPermissions, 218 'groupId': item.groupIds 219 } 220 router.pushUrl({ url: 'pages/application-secondary' }); 221 }) 222 }) 223 } 224 } 225 } 226 }.padding({ left: Constants.DEFAULT_PADDING_START, right: Constants.DEFAULT_PADDING_END, 227 top: Constants.LIST_PADDING_TOP, bottom: Constants.LIST_PADDING_BOTTOM }) 228 .margin({ bottom: Constants.LISTITEM_MARGIN_BOTTOM }) 229 .backgroundColor($r('app.color.default_background_color')) 230 .borderRadius(Constants.BORDER_RADIUS) 231 } 232 233 build() { 234 GridContainer({ gutter: Constants.GUTTER, margin: Constants.GRID_MARGIN }) { 235 Row() { 236 Row() 237 .useSizeType({ 238 xs: { span: Constants.LEFT_XS_SPAN, offset: Constants.LEFT_XS_OFFSET }, 239 sm: { span: Constants.LEFT_SM_SPAN, offset: Constants.LEFT_SM_OFFSET }, 240 md: { span: Constants.LEFT_MD_SPAN, offset: Constants.LEFT_MD_OFFSET }, 241 lg: { span: Constants.LEFT_LG_SPAN, offset: Constants.LEFT_LG_OFFSET } 242 }) 243 .height(Constants.FULL_HEIGHT) 244 Row() { 245 Column() { 246 Row() { 247 backBar( { title: JSON.stringify($r('app.string.permission_access_record')), recordable: false }) 248 } 249 Row() { 250 Column() { 251 Column() { 252 Flex({ justifyContent: FlexAlign.Start }) { 253 Text($r('app.string.record_time_limit')) 254 .margin({ left: Constants.BACKBAR_IMAGE_MARGIN_LEFT }) 255 .fontSize(Constants.TEXT_SMAL_FONT_SIZE) 256 .fontColor($r('app.color.label_color_light')) 257 .lineHeight(Constants.SUBTITLE_LINE_HEIGHT) 258 }.constraintSize({ minHeight: Constants.SUBTITLE_MIN_HEIGHT }) 259 .padding({ top: Constants.SUBTITLE_PADDING_TOP, bottom: Constants.SUBTITLE_PADDING_BOTTOM }) 260 if(this.groups.length) { 261 Stack() { 262 Tabs() { 263 TabContent() { 264 Row() { 265 Column() { 266 Scroll() { 267 Row() { 268 List() { 269 ForEach(this.groups, (item, index) => { 270 this.ListItemLayout(item, index, Constants.PERMISSION) 271 }, item => item.toString()) 272 }.padding({ top: Constants.LIST_PADDING_TOP, bottom: Constants.LIST_PADDING_BOTTOM }) 273 }.padding({ 274 left: Constants.MANAGEMENT_ROW_PADDING_LEFT, 275 right: Constants.MANAGEMENT_ROW_PADDING_RIGHT, 276 top: Constants.MANAGEMENT_ROW_PADDING_TOP 277 }) 278 }.scrollBar(BarState.Off) 279 }.width(Constants.FULL_WIDTH) 280 } 281 }.tabBar(this.TabBuilder(0)) 282 TabContent() { 283 Row() { 284 Column() { 285 Scroll() { 286 Row() { 287 List() { 288 ForEach(this.applicationInfos, (item, index) => { 289 this.ListItemLayout(item, index, Constants.APPLICATION) 290 }, item => item.toString()) 291 }.padding({ top: Constants.LIST_PADDING_TOP, bottom: Constants.LIST_PADDING_BOTTOM }) 292 }.padding({ 293 left: Constants.MANAGEMENT_ROW_PADDING_LEFT, 294 right: Constants.MANAGEMENT_ROW_PADDING_RIGHT, 295 top: Constants.MANAGEMENT_ROW_PADDING_TOP 296 }) 297 }.scrollBar(BarState.Off) 298 }.width(Constants.FULL_WIDTH) 299 } 300 }.tabBar(this.TabBuilder(1)) 301 } 302 .barWidth(Constants.BAR_WIDTH) 303 .barMode(BarMode.Fixed) 304 .onChange((index) => { 305 this.currentIndex = index 306 }) 307 }.height(Constants.FULL_HEIGHT) 308 }else { 309 Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center, direction: FlexDirection.Column }) { 310 Image($r('app.media.noRecord')) 311 .customizeImage(Constants.NORECORD_IMAGE_WIDTH, Constants.NORECORD_IMAGE_HEIGHT) 312 .margin({ left: Constants.NORECORD_IMAGE_MARGIN_LEFT }) 313 Text($r('app.string.no_record')).margin({ top: Constants.DIALOG_REQ_MARGIN_TOP }) 314 .fontSize(Constants.TEXT_SMAL_FONT_SIZE) 315 .fontColor($r('app.color.label_color_light')) 316 }.width(Constants.FULL_WIDTH).height(Constants.FULL_HEIGHT) 317 .padding({ bottom: Constants.RECORD_PADDING_BOTTOM }) 318 } 319 } 320 } 321 } 322 .layoutWeight(Constants.LAYOUT_WEIGHT) 323 } 324 } 325 .useSizeType({ 326 xs: { span: Constants.MIDDLE_XS_SPAN, offset: Constants.MIDDLE_XS_OFFSET }, 327 sm: { span: Constants.MIDDLE_SM_SPAN, offset: Constants.MIDDLE_SM_OFFSET }, 328 md: { span: Constants.MIDDLE_MD_SPAN, offset: Constants.MIDDLE_MD_OFFSET }, 329 lg: { span: Constants.MIDDLE_LG_SPAN, offset: Constants.MIDDLE_LG_OFFSET } 330 }) 331 .height(Constants.FULL_HEIGHT) 332 Row() 333 .useSizeType({ 334 xs: { span: Constants.RIGHT_XS_SPAN, offset: Constants.RIGHT_XS_OFFSET }, 335 sm: { span: Constants.RIGHT_SM_SPAN, offset: Constants.RIGHT_SM_OFFSET }, 336 md: { span: Constants.RIGHT_MD_SPAN, offset: Constants.RIGHT_MD_OFFSET }, 337 lg: { span: Constants.RIGHT_LG_SPAN, offset: Constants.RIGHT_LG_OFFSET } 338 }) 339 .height(Constants.FULL_HEIGHT) 340 } 341 .height(Constants.FULL_HEIGHT) 342 .width(Constants.FULL_WIDTH) 343 .backgroundColor($r("sys.color.ohos_id_color_sub_background")) 344 } 345 } 346 347 /** 348 * Get time 349 * @param {Number} The time stamp 350 */ 351 getTime(time, format='MM月DD日 NNHH:mm') { 352 if(this.strings.morning == 'am') { format = 'HH:mm NN MM/DD' } 353 let date = new Date(time) 354 let config = { 355 MM: date.getMonth() + 1, 356 DD: date.getDate(), 357 NN: date.getHours() >= 12 ? this.strings.afternoon : this.strings.morning, 358 HH: date.getHours() >= 12 ? date.getHours() - 12 : date.getHours(), 359 mm: date.getMinutes() > 10 ? date.getMinutes() : '0' + date.getMinutes(), 360 } 361 362 for(const key in config){ 363 format = format.replace(key,config[key]) 364 } 365 return format 366 } 367 368 /** 369 * Get application record info 370 * @param {Object} application info 371 * @param {String} groupName 372 * @param {Boolean} true: count, false: lastTime 373 */ 374 getAppRecords(appInfo, groupName, option) { 375 var record = appInfo.permissions.filter(permission => { 376 return permission.groupName == groupName 377 }) 378 return option ? record[0]['count' + appInfo.accessTokenId] : record[0]['lastTime' + appInfo.accessTokenId] 379 } 380 381 getStrings() { 382 globalThis.context.resourceManager.getString($r("app.string.visits").id, (err, val) => { 383 this.strings.visits = val 384 }) 385 globalThis.context.resourceManager.getString($r("app.string.recent_visit").id, (err, val) => { 386 this.strings.recent_visit = val 387 }) 388 globalThis.context.resourceManager.getString($r("app.string.morning").id, (err, val) => { 389 this.strings.morning = val 390 }) 391 globalThis.context.resourceManager.getString($r("app.string.afternoon").id, (err, val) => { 392 this.strings.afternoon = val 393 }) 394 } 395 396 getInfo(record, sortFlag) { 397 bundle.getBundleInfo(record.bundleName, Constants.PARMETER_BUNDLE_FLAG).then(async info => { 398 var reqUserPermissions: string[] = []; 399 var reqUserRecords: any[] = []; 400 var permissionGroups: any[] = []; 401 var acManager = abilityAccessCtrl.createAtManager() 402 var appInfo: any = {} 403 for (let j = 0; j < record.permissionRecords.length; j++) { 404 var permission = record.permissionRecords[j].permissionName; 405 try { 406 var flag = await acManager.getPermissionFlags(info.appInfo.accessTokenId, permission) 407 if(flag == Constants.PRE_AUTHORIZATION_NOT_MODIFIED) { 408 continue 409 } 410 } 411 catch(err) { 412 console.log(TAG + 'getPermissionFlags error: ' + JSON.stringify(err)) 413 } 414 if (userGrantPermissions.indexOf(permission) != -1) { 415 reqUserRecords.push(record.permissionRecords[j]) 416 } 417 } 418 for (let k = 0; k < info.reqPermissions.length; k++) { 419 var reqPermission: any = info.reqPermissions[k]; 420 try { 421 var reqFlag = await acManager.getPermissionFlags(info.appInfo.accessTokenId, reqPermission) 422 if(reqFlag == Constants.PRE_AUTHORIZATION_NOT_MODIFIED) { 423 continue 424 } 425 } 426 catch(err) { 427 console.log(TAG + 'getPermissionFlags error: ' + JSON.stringify(err)) 428 } 429 if (userGrantPermissions.indexOf(reqPermission) != -1) { 430 reqUserPermissions.push(reqPermission); 431 } 432 } 433 434 let groupNames = []; 435 let appLastTime = 0; 436 reqUserRecords.forEach(reqUserRecord => { 437 var group = getPermissionGroup(reqUserRecord.permissionName) 438 if(!group) { 439 console.info(TAG + "permission not find:" + reqUserRecord.permissionName) 440 }else { 441 var existing = permissionGroups.find(permissionGroup => permissionGroup.name == group.name) 442 var lastTime = reqUserRecord.lastAccessTime 443 lastTime > appLastTime ? appLastTime = lastTime : '' 444 if(!existing) { 445 group['count' + record.tokenId] = reqUserRecord.accessCount 446 group['lastTime' + record.tokenId] = lastTime 447 permissionGroups.push(group) 448 groupNames.push(group.groupName) 449 }else { 450 existing['count' + record.tokenId] += reqUserRecord.accessCount 451 lastTime > existing['lastTime' + record.tokenId] ? existing['lastTime' + record.tokenId] = lastTime : '' 452 } 453 } 454 }) 455 456 let groupIds = []; 457 for (let i = 0; i < reqUserPermissions.length; i++) { 458 if(groupIds.indexOf(permissionGroupIds[reqUserPermissions[i]]) == -1){ 459 groupIds.push(permissionGroupIds[reqUserPermissions[i]]); 460 } 461 } 462 463 let context = globalThis.context.createBundleContext(info.name) 464 await context.resourceManager.getString(info.appInfo.labelId, (error, value) => { 465 if (value == undefined) { 466 appInfo.groupName = info.appInfo.label; 467 } else { 468 appInfo.groupName = value; 469 } 470 }) 471 472 await context.resourceManager.getMediaBase64(info.appInfo.iconId, (error, value) => { 473 appInfo.icon = value; 474 }) 475 476 appInfo.name = info.appInfo.name 477 appInfo.api = info.targetVersion 478 appInfo.accessTokenId = info.appInfo.accessTokenId 479 appInfo.reqUserPermissions = reqUserPermissions 480 appInfo.permissions = permissionGroups 481 appInfo.groupNames = groupNames 482 appInfo.groupIds = groupIds 483 appInfo.appLastTime = appLastTime 484 this.applicationInfos.push(appInfo) 485 if(sortFlag) { 486 var appInfos: any[] = [] 487 this.applicationInfos.forEach(item => { appInfos.push(item) }) 488 appInfos.sort((a, b) => { return b.appLastTime - a.appLastTime }) 489 this.applicationInfos = appInfos 490 } 491 }) 492 } 493 494 getAllRecords() { 495 let request = { 496 "tokenId": 0, 497 "isRemote": false, 498 "deviceId": '', 499 "bundleName": '', 500 "permissionNames": [], 501 "beginTime": 0, 502 "endTime": 0, 503 "flag": 1 504 } 505 privacyManager.getPermissionUsedRecord(request).then(async records => { 506 console.info(TAG + "records: " + JSON.stringify(records.bundleRecords)) 507 var groupArray: any[] = [] 508 for (let i = 0; i < records.bundleRecords.length; i++) { 509 var record = records.bundleRecords[i] 510 try { 511 const ret = await bundle.queryAbilityByWant({ 512 bundleName: record.bundleName, 513 action: "action.system.home", 514 entities: ["entity.system.home"] 515 }, bundle.BundleFlag.GET_ABILITY_INFO_WITH_APPLICATION, Constants.USERID); 516 } catch(e) { 517 continue; 518 } 519 if (noNeedDisplayApp.indexOf(record.bundleName) != -1) { 520 continue; 521 } 522 console.info(TAG + "record: " + JSON.stringify(record)) 523 this.getInfo(record, (i + 1) == records.bundleRecords.length) 524 525 record.permissionRecords.forEach(permissionRecord => { 526 var group = getPermissionGroup(permissionRecord.permissionName) 527 if(group) { 528 var exist = groupArray.find(permissionGroup => permissionGroup.name == group.name) 529 var lastTime = permissionRecord.lastAccessTime 530 if(!exist) { 531 group.sum = permissionRecord.accessCount 532 group.recentVisit = lastTime 533 groupArray.push(group) 534 }else { 535 exist.sum += permissionRecord.accessCount 536 lastTime > exist.recentVisit ? exist.recentVisit = lastTime : '' 537 } 538 } 539 }) 540 } 541 groupArray.sort((a, b) => { return b.recentVisit - a.recentVisit }) 542 this.groups = groupArray 543 }) 544 } 545 546 aboutToAppear() { 547 this.getStrings() 548 this.getAllRecords() 549 } 550} 551