overview.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. <template>
  2. <!-- 对应页面:设备统计-概况 -->
  3. <view class="fix-top-window">
  4. <view class="uni-header">
  5. <uni-stat-breadcrumb class="uni-stat-breadcrumb-on-phone" />
  6. </view>
  7. <view class="uni-container">
  8. <view class="uni-stat--x flex p-1015">
  9. <view class="uni-stat--app-select">
  10. <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" />
  11. <uni-data-select 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" />
  12. </view>
  13. </view>
  14. <view class="uni-stat--x flex">
  15. <uni-stat-tabs label="日期选择" :current="currentDateTab" mode="date" :today="true" @change="changeTimeRange" />
  16. <uni-datetime-picker type="datetimerange" :end="new Date().getTime()" v-model="query.start_time" returnType="timestamp" :clearIcon="false" class="uni-stat-datetime-picker" :class="{'uni-stat__actived': currentDateTab < 0 && !!query.start_time.length}" @change="useDatetimePicker" />
  17. </view>
  18. <view class="uni-stat--x">
  19. <uni-stat-tabs label="平台选择" type="boldLine" mode="platform" v-model="query.platform_id" @change="changePlatform" />
  20. <uni-data-select v-if="query.platform_id && query.platform_id.indexOf('==') === -1" collection="uni-stat-app-channels" :where="channelQuery" class="p-channel" field="_id as value, channel_name as text" orderby="text asc" label="渠道/场景值选择" v-model="query.channel_id" />
  21. </view>
  22. <uni-stat-panel :items="panelData" :contrast="true" />
  23. <view class="uni-stat--x p-m">
  24. <view class="uni-stat-card-header">
  25. 趋势图
  26. </view>
  27. <uni-stat-tabs type="box" v-model="chartTab" :tabs="chartTabs" class="mb-l" @change="changeChartTab" />
  28. <view class="uni-charts-box">
  29. <qiun-data-charts type="area" :chartData="chartData" :eopts="eopts" echartsH5 echartsApp tooltipFormat="tooltipCustom" :errorMessage="errorMessage"/>
  30. </view>
  31. </view>
  32. <view class="dispaly-grid">
  33. <view class="uni-stat--x p-m">
  34. <view class="uni-stat-card-header">
  35. <view>受访页 TOP10</view>
  36. <view class="uni-stat-card-header-link" @click="navTo('/pages/uni-stat/page-res/page-res')">查看更多
  37. </view>
  38. </view>
  39. <uni-table :loading="loading" border stripe emptyText="暂无数据">
  40. <uni-tr>
  41. <block v-for="(mapper, index) in resFieldsMap" :key="index">
  42. <uni-th v-if="mapper.title" :key="index" align="center">
  43. <!-- #ifdef MP -->
  44. {{mapper.title}}
  45. <!-- #endif -->
  46. <!-- #ifndef MP -->
  47. <uni-tooltip>
  48. {{mapper.title}}
  49. <uni-icons v-if=" mapper.tooltip" type="help" color="#666" />
  50. <template v-if="mapper.tooltip" v-slot:content>
  51. <view class="uni-stat-tooltip-s">
  52. {{mapper.tooltip}}
  53. </view>
  54. </template>
  55. </uni-tooltip>
  56. <!-- #endif -->
  57. </uni-th>
  58. </block>
  59. </uni-tr>
  60. <uni-tr v-for="(item ,i) in resTableData" :key="i">
  61. <block v-for="(mapper, index) in resFieldsMap" :key='index'>
  62. <uni-td v-if="mapper.title" :key="index" :align="index === 0 ? 'left' : 'center'">
  63. {{item[mapper.field] !== undefined ? item[mapper.field] : '-'}}
  64. </uni-td>
  65. </block>
  66. </uni-tr>
  67. </uni-table>
  68. </view>
  69. <view class="uni-stat--x uni-stat-card p-m">
  70. <view class="uni-stat-card-header">
  71. <view>入口页 TOP10</view>
  72. <view class="uni-stat-card-header-link" @click="navTo('/pages/uni-stat/page-ent/page-ent')">查看更多
  73. </view>
  74. </view>
  75. <uni-table :loading="loading" border stripe emptyText="暂无数据">
  76. <uni-tr>
  77. <block v-for="(mapper, index) in entFieldsMap" :key="index">
  78. <uni-th v-if="mapper.title" :key="index" align="center">
  79. <!-- #ifdef MP -->
  80. {{mapper.title}}
  81. <!-- #endif -->
  82. <!-- #ifndef MP -->
  83. <uni-tooltip>
  84. {{mapper.title}}
  85. <uni-icons v-if=" mapper.tooltip" type="help" color="#666" />
  86. <template v-if="mapper.tooltip" v-slot:content>
  87. <view class="uni-stat-tooltip-s">
  88. {{mapper.tooltip}}
  89. </view>
  90. </template>
  91. </uni-tooltip>
  92. <!-- #endif -->
  93. </uni-th>
  94. </block>
  95. </uni-tr>
  96. <uni-tr v-for="(item ,i) in entTableData" :key="i">
  97. <block v-for="(mapper, index) in entFieldsMap" :key="index">
  98. <uni-td v-if="mapper.title" :key="index" :align="index === 0 ? 'left' : 'center'">
  99. {{item[mapper.field] !== undefined ? item[mapper.field] : '-'}}
  100. </uni-td>
  101. </block>
  102. </uni-tr>
  103. </uni-table>
  104. </view>
  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. stringifyField,
  117. stringifyGroupField,
  118. getTimeOfSomeDayAgo,
  119. division,
  120. format,
  121. formatDate,
  122. parseDateTime,
  123. getFieldTotal,
  124. debounce
  125. } from '@/js_sdk/uni-stat/util.js'
  126. import {
  127. fieldsMap,
  128. resFieldsMap,
  129. entFieldsMap
  130. } from './fieldsMap.js'
  131. const panelOption = fieldsMap.filter(f => f.hasOwnProperty('value'))
  132. export default {
  133. data() {
  134. return {
  135. tableName: 'uni-stat-result',
  136. fieldsMap,
  137. resFieldsMap,
  138. entFieldsMap,
  139. query: {
  140. dimension: 'hour',
  141. appid: '',
  142. version_id: '',
  143. platform_id: '',
  144. uni_platform:'',
  145. start_time: [],
  146. channel_id: ''
  147. },
  148. options: {
  149. pageCurrent: 1, // 当前页
  150. total: 0, // 数据总量
  151. pageSizeIndex: 0, // 与 pageSizeRange 一起计算得出 pageSize
  152. pageSizeRange: [10, 20, 50, 100],
  153. },
  154. errorMessage: "",
  155. loading: false,
  156. currentDateTab: 2,
  157. chartTab: 'new_user_count',
  158. tableData: [],
  159. resTableData: [],
  160. entTableData: [],
  161. panelData: panelOption,
  162. chartData: {},
  163. eopts: {
  164. seriesTemplate: [{
  165. itemStyle: {
  166. borderWidth: 2,
  167. borderColor: '#1890FF',
  168. color: '#1890FF'
  169. },
  170. areaStyle: {
  171. color: {
  172. colorStops: [{
  173. offset: 0,
  174. color: '#1890FF', // 0% 处的颜色
  175. }, {
  176. offset: 1,
  177. color: '#FFFFFF' // 100% 处的颜色
  178. }]
  179. }
  180. }
  181. }, {
  182. // smooth: false,
  183. lineStyle: {
  184. color: '#ea7ccc',
  185. width: 2,
  186. type: 'dashed'
  187. },
  188. itemStyle: {
  189. borderWidth: 1,
  190. borderColor: '#ea7ccc',
  191. color: '#ea7ccc'
  192. },
  193. areaStyle: null
  194. }]
  195. },
  196. tabIndex: 0,
  197. tabName: '新增设备',
  198. }
  199. },
  200. onLoad(option) {
  201. const {
  202. appid
  203. } = option
  204. if (appid) {
  205. this.query.appid = appid
  206. }
  207. },
  208. computed: {
  209. pageSize() {
  210. const {
  211. pageSizeRange,
  212. pageSizeIndex
  213. } = this.options
  214. return pageSizeRange[pageSizeIndex]
  215. },
  216. chartTabs() {
  217. const tabs = []
  218. fieldsMap.forEach(item => {
  219. const _id = item.field
  220. const name = item.title
  221. if (_id && name) {
  222. tabs.push({
  223. _id,
  224. name
  225. })
  226. }
  227. })
  228. return tabs
  229. },
  230. versionQuery() {
  231. const {
  232. appid,
  233. uni_platform
  234. } = this.query
  235. const query = stringifyQuery({
  236. appid,
  237. uni_platform,
  238. })
  239. return query
  240. },
  241. channelQuery() {
  242. const {
  243. appid,
  244. platform_id,
  245. } = this.query
  246. const query = stringifyQuery({
  247. appid,
  248. platform_id
  249. })
  250. return query
  251. },
  252. },
  253. created() {
  254. this.debounceGet = debounce(() => {
  255. this.getAllData(this.query);
  256. }, 300);
  257. },
  258. watch: {
  259. query: {
  260. deep: true,
  261. handler(val) {
  262. this.options.pageCurrent = 1 // 重置分页
  263. this.debounceGet()
  264. }
  265. }
  266. },
  267. methods: {
  268. useDatetimePicker() {
  269. this.currentDateTab = null
  270. },
  271. changePlatform(id, index, name, item) {
  272. this.query.version_id = 0
  273. this.query.uni_platform = item.code
  274. },
  275. changeTimeRange(id, index) {
  276. this.currentDateTab = index
  277. const day = 24 * 60 * 60 * 1000
  278. let start, end
  279. start = getTimeOfSomeDayAgo(id)
  280. if (!id) {
  281. end = getTimeOfSomeDayAgo(0) + day - 1
  282. } else {
  283. end = getTimeOfSomeDayAgo(0) - 1
  284. }
  285. this.query.start_time = [start, end]
  286. },
  287. changePageCurrent(e) {
  288. this.options.pageCurrent = e.current
  289. this.getChartData(this.query)
  290. },
  291. changePageSize(e) {
  292. const {
  293. value
  294. } = e.detail
  295. this.options.pageCurrent = 1 // 重置分页
  296. this.options.pageSizeIndex = value
  297. this.getChartData(this.query)
  298. },
  299. changeChartTab(id, index, name) {
  300. this.tabIndex = index
  301. this.tabName = name
  302. this.getChartData(this.query, id, name)
  303. },
  304. getAllData(query) {
  305. if (!query.appid) {
  306. this.errorMessage = "请先选择应用";
  307. return; // 如果appid为空,则不进行查询
  308. }
  309. this.errorMessage = "";
  310. this.getPanelData()
  311. this.getChartData(query)
  312. this.getPageData(query, 'res')
  313. this.getPageData(query, 'ent')
  314. },
  315. getDays() {
  316. if (!this.query.start_time.length) return true
  317. const day = 24 * 60 * 60 * 1000
  318. const [start, end] = this.query.start_time
  319. const lessTwoDay = end - start >= day
  320. return lessTwoDay
  321. },
  322. getPanelData() {
  323. const {
  324. appid,
  325. platform_id,
  326. version_id,
  327. channel_id
  328. } = this.query
  329. let query = stringifyQuery({
  330. appid,
  331. platform_id,
  332. version_id,
  333. channel_id,
  334. start_time: [getTimeOfSomeDayAgo(1), new Date().getTime()]
  335. })
  336. const db = uniCloud.database()
  337. const subTable = db.collection(this.tableName)
  338. .where(query)
  339. .field(
  340. `${stringifyField(fieldsMap)},dimension,stat_date`
  341. )
  342. .groupBy(`stat_date, dimension`)
  343. .groupField(stringifyGroupField(fieldsMap))
  344. .orderBy('stat_date', 'desc')
  345. .get()
  346. .then(res => {
  347. const data = res.result.data
  348. const today = data.find(item => item.stat_date === parseDateTime(getTimeOfSomeDayAgo(0), '',
  349. '')) || {}
  350. today.total_devices = 0
  351. const yesterday = data.find(item => item.dimension === 'day' && item.stat_date ===
  352. parseDateTime(getTimeOfSomeDayAgo(1), '', ''))
  353. this.panelData = []
  354. this.panelData = mapfields(fieldsMap, today)
  355. this.panelData.map(item => {
  356. mapfields(fieldsMap, yesterday, item, '', 'contrast')
  357. })
  358. getFieldTotal.call(this, query)
  359. })
  360. },
  361. getChartData(query, field = this.chartTabs[this.tabIndex]._id, name = this.chartTabs[this.tabIndex].name) {
  362. // this.chartData = {}
  363. const {
  364. pageCurrent
  365. } = this.options
  366. const days = this.currentDateTab
  367. const date = getTimeOfSomeDayAgo(days)
  368. const day = 24 * 60 * 60 * 1000
  369. let start_time
  370. // 范围小于一天的日期(今日、昨日)做增大一天处理,目的是对比当前日期和前一天(今日和昨日、昨日和上前日)
  371. if (!this.getDays()) {
  372. const start = date - day
  373. const end = date + day - 1
  374. query = JSON.parse(JSON.stringify(query))
  375. start_time = query.start_time = [start, end]
  376. // query.dimension = 'hour'
  377. }
  378. query = stringifyQuery(query, true, ['uni_platform'])
  379. const db = uniCloud.database()
  380. db.collection(this.tableName)
  381. .where(query)
  382. .field(`${stringifyField(fieldsMap, field)}, start_time`)
  383. .groupBy(`start_time`)
  384. .groupField(stringifyGroupField(fieldsMap, field))
  385. .orderBy('start_time', 'asc')
  386. .get({
  387. getCount: true
  388. })
  389. .then(res => {
  390. const {
  391. count,
  392. data
  393. } = res.result
  394. const options = {
  395. categories: [],
  396. series: [{
  397. name,
  398. data: []
  399. }]
  400. }
  401. let mapper = fieldsMap.filter(f => f.field === field)
  402. mapper = JSON.parse(JSON.stringify(mapper))
  403. delete mapper[0].value
  404. mapper[0].formatter = ''
  405. if (!this.getDays()) {
  406. const [start, end] = start_time
  407. const line = options.series[1] = {
  408. name: formatDate(start),
  409. data: [],
  410. }
  411. const cont = options.series[0] = {
  412. name: formatDate(end),
  413. data: [],
  414. }
  415. for (let i = 0; i < 24; ++i) {
  416. const hour = i < 10 ? '0' + i : i
  417. const x = `${hour}:00 ~ ${hour}:59`
  418. options.categories.push(x)
  419. line.data[i] = 0
  420. cont.data[i] = 0
  421. data.forEach(item => {
  422. mapfields(mapper, item, item)
  423. let val = Number(item[field])
  424. const d = new Date(item.start_time)
  425. if (item.start_time < date) {
  426. if (d.getHours() === i) {
  427. line.data[i] = val
  428. }
  429. } else {
  430. if (d.getHours() === i) {
  431. cont.data[i] = val
  432. }
  433. }
  434. })
  435. }
  436. } else {
  437. for (const item of data) {
  438. mapfields(mapper, item, item)
  439. const x = formatDate(item.start_time, 'day')
  440. let y = Number(item[field])
  441. options.series[0].data.push(y)
  442. options.categories.push(x)
  443. }
  444. }
  445. this.chartData = options
  446. }).catch((err) => {
  447. console.error(err)
  448. // err.message 错误信息
  449. // err.code 错误码
  450. }).finally(() => {})
  451. },
  452. getAppAccessTimes(query) {
  453. const db = uniCloud.database()
  454. return db.collection(this.tableName)
  455. .where(query)
  456. .groupBy('appid')
  457. .groupField(`sum(page_visit_count) as total_app_access`)
  458. .get()
  459. },
  460. getPageData(query, type) {
  461. query = JSON.parse(JSON.stringify(query))
  462. query.dimension = 'day'
  463. query = stringifyQuery(query,false,['uni_platform'])
  464. const {
  465. pageCurrent
  466. } = this.options
  467. const mapping = this[`${type}FieldsMap`]
  468. const field = mapping[1].field
  469. this.loading = true
  470. const db = uniCloud.database()
  471. const filterAppid = stringifyQuery({
  472. appid: this.query.appid
  473. })
  474. const mainTableTemp = db.collection('uni-stat-pages')
  475. .where(filterAppid)
  476. .field('_id, title, path')
  477. .getTemp()
  478. const subTableTemp = db.collection('uni-stat-page-result')
  479. .where(`${query} && ${field} > 0`)
  480. .getTemp()
  481. db.collection(subTableTemp, mainTableTemp)
  482. .field(
  483. `${stringifyField(mapping, field)}, stat_date, page_id`
  484. )
  485. .groupBy("page_id")
  486. .groupField(stringifyGroupField(mapping, field))
  487. .orderBy(field, 'desc')
  488. .limit(10)
  489. .get({
  490. getCount: true
  491. })
  492. .then(res => {
  493. const {
  494. count,
  495. data
  496. } = res.result
  497. let total_app_access
  498. this.getAppAccessTimes(query).then(res => {
  499. const data = res.result.data[0]
  500. total_app_access = data && data.total_app_access
  501. }).finally(() => {
  502. this[`${type}TableData`] = []
  503. for (const item of data) {
  504. item.total_app_access = total_app_access
  505. const lines = item.page_id
  506. if (Array.isArray(lines)) {
  507. delete(item.page_id)
  508. const line = lines[0]
  509. if (line && Object.keys(line).length) {
  510. for (const key in line) {
  511. if (key !== '_id') {
  512. item[key] = line[key]
  513. }
  514. }
  515. }
  516. }
  517. mapfields(mapping, item, item)
  518. this[`${type}TableData`].push(item)
  519. }
  520. this.loading = false
  521. })
  522. }).catch((err) => {
  523. console.error(err)
  524. // err.message 错误信息
  525. // err.code 错误码
  526. }).finally(() => {})
  527. },
  528. navTo(url) {
  529. if (!url) return
  530. uni.navigateTo({
  531. url
  532. })
  533. }
  534. }
  535. }
  536. </script>
  537. <style>
  538. .uni-stat-card-header {
  539. display: flex;
  540. justify-content: space-between;
  541. color: #555;
  542. font-size: 14px;
  543. font-weight: 600;
  544. padding: 10px 0;
  545. margin-bottom: 15px;
  546. }
  547. .uni-stat-card-header-link {
  548. cursor: pointer;
  549. }
  550. </style>