app.vue 17 KB

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