app.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <template>
  2. <!-- 对应页面: app崩溃 -->
  3. <view class="fix-top-window">
  4. <view class="uni-header">
  5. <uni-stat-breadcrumb class="uni-stat-breadcrumb-on-phone" />
  6. <view class="uni-group hide-on-phone">
  7. <!-- <view class="uni-title">崩溃分析</view> -->
  8. <view class="uni-sub-title">开发者可以在这里快速查询原生应用最近出现的具体崩溃内容,了解崩溃概况信息,以便快速修复问题</view>
  9. </view>
  10. </view>
  11. <view class="uni-container">
  12. <view class="uni-stat--x flex">
  13. <uni-data-select collection="opendb-app-list" field="appid as value, name as text" orderby="text asc"
  14. :defItem="1" label="应用选择" v-model="query.appid" :clear="false" />
  15. <uni-data-select collection="opendb-app-versions" :where="versionQuery"
  16. field="_id as value, version as text" orderby="text asc" label="版本选择" v-model="query.version_id" />
  17. <uni-stat-tabs label="平台选择" type="boldLine" :all="false" mode="platform-channel"
  18. v-model="query.platform_id" @change="changePlatform" />
  19. <view class="flex">
  20. <uni-stat-tabs label="日期选择" :current="currentDateTab" :yesterday="false" mode="date"
  21. @change="changeTimeRange" />
  22. <uni-datetime-picker type="daterange" :end="new Date().getTime()" v-model="query.start_time"
  23. returnType="timestamp" :clearIcon="false" class="uni-stat-datetime-picker"
  24. :class="{'uni-stat__actived': currentDateTab < 0 && !!query.start_time.length}"
  25. @change="useDatetimePicker" />
  26. </view>
  27. </view>
  28. <view class="uni-stat--x" style="padding: 15px 0;">
  29. <uni-stat-panel :items="panelData" class="uni-stat-panel" />
  30. <uni-stat-tabs type="box" v-model="chartTab" :tabs="chartTabs" class="mb-l" />
  31. <view class="uni-charts-box">
  32. <qiun-data-charts type="area" :chartData="chartData" :eopts="{notMerge:true}" echartsH5 echartsApp
  33. tooltipFormat="tooltipCustom" />
  34. </view>
  35. </view>
  36. <view class="uni-stat--x p-m">
  37. <view class="flex-between">
  38. <view class="uni-stat-card-header">信息列表</view>
  39. <view class="uni-group">
  40. <!-- #ifdef H5 -->
  41. <download-excel class="hide-on-phone" :fields="exportExcel.fields" :data="exportExcelData"
  42. :type="exportExcel.type" :name="exportExcel.filename">
  43. <button class="uni-button" type="primary" size="mini">导出 Excel</button>
  44. </download-excel>
  45. <!-- #endif -->
  46. </view>
  47. </view>
  48. <unicloud-db ref="udb" :collection="collectionList"
  49. field="appid,version,platform,channel,sdk_version,device_id,device_net,device_os,device_os_version,device_vendor,device_model,device_is_root,device_os_name,device_batt_level,device_batt_temp,device_memory_use_size,device_memory_total_size,device_disk_use_size,device_disk_total_size,device_abis,app_count,app_use_memory_size,app_webview_count,app_use_duration,app_run_fore,package_name,package_version,page_url,error_msg,create_time"
  50. :where="where" page-data="replace" :orderby="orderby" :getcount="true" :page-size="options.pageSize"
  51. :page-current="options.pageCurrent" loadtime="manual"
  52. v-slot:default="{data,pagination,loading,error,options}" :options="options" @load="onqueryload">
  53. <uni-table ref="table" :loading="loading" border stripe :emptyText="$t('common.empty')"
  54. style="overflow-y: scroll;">
  55. <uni-tr>
  56. <template v-for="(mapper, index) in fieldsMap">
  57. <!-- todo: schema table -->
  58. <!-- <uni-th v-if="mapper.title" :key="index" :filter-type="mapper.filter"
  59. @filter-change="filterChange($event, mapper.field)" sortable
  60. @sort-change="sortChange($event, mapper.field)" align="center"
  61. :style="`min-width: ${mapper.title.length * 15 + 80}px;`"> -->
  62. <uni-th v-if="mapper.title" :key="index" align="center"
  63. :style="{'minWidth':`${mapper.title.length * 15 + 80}px`}">
  64. <!-- #ifdef MP -->
  65. {{mapper.title}}
  66. <!-- #endif -->
  67. <!-- #ifndef MP -->
  68. <uni-tooltip>
  69. {{mapper.title}}
  70. <uni-icons v-if="mapper.tooltip" type="help" color="#666" />
  71. <template v-if="mapper.tooltip" v-slot:content>
  72. <view class="uni-stat-tooltip-s">
  73. {{mapper.tooltip}}
  74. </view>
  75. </template>
  76. </uni-tooltip>
  77. <!-- #endif -->
  78. </uni-th>
  79. </template>
  80. </uni-tr>
  81. <uni-tr v-for="(item ,i) in tableData" :key="i">
  82. <template v-for="(mapper, index) in fieldsMap">
  83. <uni-td v-if="mapper.field === 'error_msg'" :key="mapper.field" align="left"
  84. style="min-width: 500px;">
  85. <!-- #ifdef MP -->
  86. {{item.error_msg ? item.error_msg.substring(0, 100) + '...' : '-'}}
  87. <!-- #endif -->
  88. <!-- #ifndef MP -->
  89. <uni-tooltip>
  90. {{item.error_msg ? item.error_msg.substring(0, 100) + '...' : ''}}
  91. <uni-icons v-if="item.error_msg" type="help" color="#666" />
  92. <template v-if="item.error_msg" v-slot:content>
  93. <view class="uni-stat-tooltip-l">
  94. {{item.error_msg}}
  95. </view>
  96. </template>
  97. </uni-tooltip>
  98. <!-- #endif -->
  99. </uni-td>
  100. <uni-td v-else-if="mapper.field === 'create_time'" :key="mapper.field" align="center">
  101. <uni-dateformat :threshold="[0, 0]" :date="item.create_time"></uni-dateformat>
  102. </uni-td>
  103. <uni-td v-else :key="mapper.field" align="center">
  104. {{item[mapper.field] !== undefined ? item[mapper.field] : '-'}}
  105. </uni-td>
  106. </template>
  107. </uni-tr>
  108. </uni-table>
  109. <view class="uni-pagination-box">
  110. <uni-pagination show-icon :page-size="pagination.size" v-model="pagination.current"
  111. :total="pagination.count" @change="onPageChanged" />
  112. </view>
  113. </unicloud-db>
  114. </view>
  115. </view>
  116. <!-- #ifndef H5 -->
  117. <fix-window />
  118. <!-- #endif -->
  119. </view>
  120. </template>
  121. <script>
  122. import {
  123. mapfields,
  124. stringifyQuery,
  125. getTimeOfSomeDayAgo,
  126. division,
  127. format,
  128. formatDate,
  129. parseDateTime,
  130. debounce,
  131. getAllDateCN
  132. } from '@/js_sdk/uni-stat/util.js'
  133. import {
  134. fieldsMap,
  135. } from './fieldsMap.js'
  136. const panelOption = [{
  137. title: '崩溃总数',
  138. field: 'count',
  139. value: 0,
  140. formatter: ',',
  141. tooltip: '指原生应用在某个时间段内出现崩溃的总数'
  142. }, {
  143. title: '崩溃率',
  144. field: 'count/app_launch_count',
  145. computed: 'count/app_launch_count',
  146. formatter: '%',
  147. value: 0,
  148. tooltip: '时间范围内的总崩溃数/原生应用启动次数,如果小于0.01%,默认显示为0'
  149. }]
  150. import {
  151. enumConverter,
  152. filterToWhere
  153. } from '@/js_sdk/validator/uni-stat-app-crash-logs.js';
  154. const db = uniCloud.database()
  155. // 表查询配置
  156. const dbOrderBy = 'create_time desc' // 排序字段
  157. const dbSearchFields = [] // 模糊搜索字段,支持模糊搜索的字段列表。联表查询格式: 主表字段名.副表字段名,例如用户表关联角色表 role.role_name
  158. // 分页配置
  159. const pageSize = 20
  160. const pageCurrent = 1
  161. const orderByMapping = {
  162. "ascending": "asc",
  163. "descending": "desc"
  164. }
  165. export default {
  166. data() {
  167. return {
  168. fieldsMap,
  169. //todo:要与schema 生成页面一起工作,stringifyQuery 需要与 schema 查询逻辑相容
  170. query: {
  171. type: "crash",
  172. dimension: "day",
  173. appid: "",
  174. platform_id: '',
  175. uni_platform: '',
  176. version_id: '',
  177. start_time: [],
  178. },
  179. options: {
  180. pageCurrent: 1, // 当前页
  181. total: 0, // 数据总量
  182. pageSizeIndex: 0, // 与 pageSizeRange 一起计算得出 pageSize
  183. pageSizeRange: [10, 20, 50, 100],
  184. },
  185. loading: false,
  186. popupLoading: false,
  187. currentDateTab: 0,
  188. // currentChartTab: ,
  189. tableData: [],
  190. popupTableData: [],
  191. panelData: JSON.parse(JSON.stringify(panelOption)),
  192. chartData: {},
  193. chartTab: 'errorCount',
  194. chartTabs: [{
  195. _id: 'errorCount',
  196. name: '崩溃次数'
  197. }, {
  198. _id: 'errorRate',
  199. name: '崩溃率'
  200. }],
  201. collectionList: "uni-stat-app-crash-logs",
  202. schemaQuery: '',
  203. where: this.tableData,
  204. orderby: dbOrderBy,
  205. orderByFieldName: "",
  206. selectedIndexs: [],
  207. options: {
  208. pageSize,
  209. pageCurrent,
  210. filterData: {},
  211. ...enumConverter
  212. },
  213. exportExcel: {
  214. "filename": "uni-stat-app-crash-logs.xls",
  215. "type": "xls",
  216. "fields": {
  217. "appid": "appid",
  218. "version": "version",
  219. "platform": "platform",
  220. "channel": "channel",
  221. "sdk_version": "sdk_version",
  222. "device_id": "device_id",
  223. "device_net": "device_net",
  224. "device_os": "device_os",
  225. "device_os_version": "device_os_version",
  226. "device_vendor": "device_vendor",
  227. "device_model": "device_model",
  228. "device_is_root": "device_is_root",
  229. "device_os_name": "device_os_name",
  230. "device_batt_level": "device_batt_level",
  231. "device_batt_temp": "device_batt_temp",
  232. "device_memory_use_size": "device_memory_use_size",
  233. "device_memory_total_size": "device_memory_total_size",
  234. "device_disk_use_size": "device_disk_use_size",
  235. "device_disk_total_size": "device_disk_total_size",
  236. "device_abis": "device_abis",
  237. "app_count": "app_count",
  238. "app_use_memory_size": "app_use_memory_size",
  239. "app_webview_count": "app_webview_count",
  240. "app_use_duration": "app_use_duration",
  241. "app_run_fore": "app_run_fore",
  242. "package_name": "package_name",
  243. "package_version": "package_version",
  244. "page_url": "page_url",
  245. "error_msg": "error_msg",
  246. "create_time": "create_time"
  247. }
  248. },
  249. exportExcelData: []
  250. }
  251. },
  252. computed: {
  253. queryStr() {
  254. return stringifyQuery(this.query)
  255. },
  256. tableQuery() {
  257. const {
  258. appid,
  259. platform_id,
  260. version_id,
  261. start_time
  262. } = this.query
  263. // 从本地存储中取到数据做过滤
  264. const platforms = uni.getStorageSync('platform_channel_last_data')
  265. const versions = uni.getStorageSync('uni-stat-app-versions_last_data')
  266. const p = Array.isArray(platforms) && platforms.find(p => p._id === platform_id)
  267. const v = Array.isArray(versions) && versions.find(v => v._id === version_id)
  268. const query = stringifyQuery({
  269. appid,
  270. create_time: start_time,
  271. platform: p && p.code || '',
  272. version: v && v.text || ''
  273. })
  274. return query
  275. },
  276. versionQuery() {
  277. const {
  278. appid,
  279. uni_platform
  280. } = this.query
  281. const query = stringifyQuery({
  282. appid,
  283. uni_platform,
  284. type: 'native_app'
  285. })
  286. return query
  287. }
  288. },
  289. created() {
  290. this.debounceGet = debounce(() => {
  291. this.getAllData(this.queryStr)
  292. this.where = this.tableQuery
  293. this.$nextTick(() => {
  294. this.$refs.udb && this.$refs.udb.loadData()
  295. }, 200)
  296. })
  297. },
  298. watch: {
  299. query: {
  300. deep: true,
  301. handler(val) {
  302. this.options.pageCurrent = 1 // 重置分页
  303. this.debounceGet()
  304. }
  305. },
  306. chartTab(val) {
  307. this.getChartData(this.queryStr)
  308. }
  309. },
  310. onLoad() {
  311. this._filter = {}
  312. },
  313. methods: {
  314. onqueryload(data) {
  315. this.exportExcelData = data
  316. this.tableData = data
  317. },
  318. getWhere() {
  319. const query = this.schemaQuery.trim()
  320. if (!query) {
  321. return ''
  322. }
  323. const queryRe = new RegExp(query, 'i')
  324. return dbSearchFields.map(name => queryRe + '.test(' + name + ')').join(' || ')
  325. },
  326. loadData(clear = true) {
  327. this.$refs.udb.loadData({
  328. clear
  329. })
  330. },
  331. onPageChanged(e) {
  332. this.selectedIndexs.length = 0
  333. this.$refs.table.clearSelection()
  334. this.$refs.udb.loadData({
  335. current: e.current
  336. })
  337. },
  338. sortChange(e, name) {
  339. this.orderByFieldName = name;
  340. if (e.order) {
  341. this.orderby = name + ' ' + orderByMapping[e.order]
  342. } else {
  343. this.orderby = ''
  344. }
  345. this.$refs.table.clearSelection()
  346. this.$nextTick(() => {
  347. this.$refs.udb.loadData()
  348. })
  349. },
  350. filterChange(e, name) {
  351. this._filter[name] = {
  352. type: e.filterType,
  353. value: e.filter
  354. }
  355. let newWhere = filterToWhere(this._filter, db.command)
  356. if (Object.keys(newWhere).length) {
  357. this.where = newWhere
  358. } else {
  359. this.where = ''
  360. // this.where = this.tableQuery
  361. }
  362. this.$nextTick(() => {
  363. this.$refs.udb.loadData()
  364. })
  365. },
  366. useDatetimePicker() {
  367. this.currentDateTab = -1
  368. },
  369. changePlatform(id, index, name, item) {
  370. this.query.version_id = 0
  371. this.query.uni_platform = item.code
  372. },
  373. changeTimeRange(id, index) {
  374. this.currentDateTab = index
  375. const start = getTimeOfSomeDayAgo(id),
  376. end = getTimeOfSomeDayAgo(0) - 1
  377. this.query.start_time = [start, end]
  378. },
  379. getAllData(query) {
  380. this.getPanelData(query)
  381. this.getChartData(query)
  382. },
  383. getPanelData(query) {
  384. // console.log(query);
  385. let querystr = stringifyQuery(this.query, false, ['uni_platform'])
  386. const db = uniCloud.database()
  387. console.log('queryStr', querystr);
  388. db.collection('uni-stat-error-result')
  389. .where(querystr)
  390. .field('count as temp_count, app_launch_count as temp_app_launch_count, appid')
  391. .groupBy('appid')
  392. .groupField('sum(temp_count) as count, sum(temp_app_launch_count) as app_launch_count')
  393. .get({
  394. getCount: true
  395. })
  396. .then(res => {
  397. const {
  398. count,
  399. data
  400. } = res.result
  401. const item = res.result.data[0]
  402. // this.panelData = []
  403. let queryTemp = Object.assign({}, this.query)
  404. delete queryTemp.type
  405. console.log('---- query ', queryTemp);
  406. this.getTotalLaunch(stringifyQuery(queryTemp, false, ['uni_platform'])).then(res => {
  407. const total = res.result.data[0]
  408. console.log('result total---', total);
  409. if (item) {
  410. let launch_count = total && total.total_app_launch_count
  411. item.app_launch_count = launch_count
  412. this.panelData = mapfields(panelOption, item)
  413. }
  414. })
  415. })
  416. },
  417. getTotalLaunch(query) {
  418. const db = uniCloud.database()
  419. return db.collection('uni-stat-result')
  420. .where(query)
  421. .groupBy('appid')
  422. .groupField('sum(app_launch_count) as total_app_launch_count')
  423. .get()
  424. },
  425. getChartData(query, field = 'day_count') {
  426. let querystr = stringifyQuery(this.query, false, ['uni_platform'])
  427. this.chartData = {}
  428. const {
  429. pageCurrent
  430. } = this.options
  431. const db = uniCloud.database()
  432. const [start_time, end_tiem] = this.query.start_time
  433. // 时间补全
  434. const timeAll = getAllDateCN(new Date(start_time), new Date(end_tiem))
  435. db.collection('uni-stat-error-result')
  436. .where(querystr)
  437. .field('count as temp_count, app_launch_count as temp_app_launch_count, start_time')
  438. .groupBy('start_time')
  439. .groupField('sum(temp_count) as count, sum(temp_app_launch_count) as app_launch_count')
  440. .orderBy('start_time', 'asc')
  441. .get({
  442. getCount: true
  443. })
  444. .then(res => {
  445. const {
  446. count,
  447. data
  448. } = res.result
  449. let dataAll = []
  450. timeAll.forEach(v => {
  451. let item = data.find(item => item.start_time === v)
  452. console.log(item);
  453. if (item) {
  454. dataAll.push(item)
  455. } else {
  456. dataAll.push({
  457. app_launch_count: 0,
  458. count: 0,
  459. start_time: v
  460. })
  461. }
  462. })
  463. const options = {
  464. categories: [],
  465. series: [{
  466. name: '暂无数据',
  467. data: []
  468. }]
  469. }
  470. if (this.chartTab === 'errorCount') {
  471. const countLine = options.series[0] = {
  472. name: '崩溃次数',
  473. data: []
  474. }
  475. const xAxis = options.categories
  476. for (const item of dataAll) {
  477. let date = item.start_time
  478. const x = formatDate(date, 'day')
  479. const countY = item.count
  480. xAxis.push(x)
  481. countLine.data.push(countY)
  482. }
  483. this.chartData = options
  484. } else {
  485. const rateLine = options.series[0] = {
  486. name: '崩溃率(%)',
  487. data: [],
  488. lineStyle: {
  489. color: '#EE6666',
  490. width: 1,
  491. },
  492. itemStyle: {
  493. borderWidth: 1,
  494. borderColor: '#EE6666',
  495. color: '#EE6666'
  496. },
  497. areaStyle: {
  498. color: {
  499. colorStops: [{
  500. offset: 0,
  501. color: '#EE6666', // 0% 处的颜色
  502. }, {
  503. offset: 1,
  504. color: '#FFFFFF' // 100% 处的颜色
  505. }]
  506. }
  507. }
  508. }
  509. const xAxis = options.categories
  510. for (const item of dataAll) {
  511. const {
  512. count,
  513. app_launch_count
  514. } = item
  515. let date = item.start_time
  516. const x = formatDate(date, 'day')
  517. console.log('---', x);
  518. xAxis.push(x)
  519. let y = count / app_launch_count
  520. y = !y ? 0 : y.toFixed(2)
  521. rateLine.data.push(y)
  522. }
  523. this.chartData = options
  524. }
  525. }).finally(() => {})
  526. }
  527. }
  528. }
  529. </script>
  530. <style>
  531. .flex-between {
  532. margin-bottom: 10px;
  533. display: flex;
  534. justify-content: space-between;
  535. align-items: center;
  536. }
  537. .uni-stat-panel {
  538. box-shadow: unset;
  539. border-bottom: 1px solid #eee;
  540. padding: 0;
  541. margin: 0 15px;
  542. }
  543. .uni-stat-tooltip-s {
  544. width: 160px;
  545. white-space: normal;
  546. }
  547. </style>