stickiness.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  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 class="uni-group">
  7. <view class="uni-sub-title hide-on-phone">用户忠诚度用户对您应用的访问深度及访问频次情况。助您了解用户对应用的粘度,尤其在对内容改进后,效果是否有所提升</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="应用选择" @change="changeAppid" v-model="query.appid" :clear="false" />
  14. <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" />
  15. </view>
  16. </view>
  17. <view class="uni-stat--x flex">
  18. <uni-stat-tabs label="日期选择" :current="currentDateTab" mode="date" @change="changeTimeRange" />
  19. <uni-datetime-picker type="datetimerange" :end="new Date().getTime()" v-model="query.start_time"
  20. returnType="timestamp" :clearIcon="false" class="uni-stat-datetime-picker"
  21. :class="{'uni-stat__actived': currentDateTab < 0 && !!query.start_time.length}"
  22. @change="useDatetimePicker" />
  23. </view>
  24. <view class="uni-stat--x">
  25. <uni-stat-tabs label="平台选择" type="boldLine" mode="platform" v-model="query.platform_id"
  26. @change="changePlatform" />
  27. <uni-data-select ref="version-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" />
  28. </view>
  29. <view class="uni-stat--x mb-l" style="padding-top: 0;">
  30. <view class="mb-m line-bottom">
  31. <uni-stat-tabs type="boldLine" :tabs="types" v-model="type"
  32. style="line-height: 40px; margin-bottom: -17px;" />
  33. </view>
  34. <view class="p-m">
  35. <view class="uni-charts-box">
  36. <qiun-data-charts type="pie" :chartData="chartData" echartsH5 echartsApp :errorMessage="errorMessage"/>
  37. </view>
  38. </view>
  39. </view>
  40. <view class="uni-stat--x p-m">
  41. <uni-stat-table :data="tableData" :filedsMap="fieldsMap" :loading="loading" />
  42. </view>
  43. </view>
  44. <!-- #ifndef H5 -->
  45. <fix-window />
  46. <!-- #endif -->
  47. </view>
  48. </template>
  49. <script>
  50. import {
  51. mapfields,
  52. stringifyQuery,
  53. getTimeOfSomeDayAgo,
  54. division,
  55. format,
  56. debounce
  57. } from '@/js_sdk/uni-stat/util.js'
  58. import fieldsMap from './fieldsMap.js'
  59. export default {
  60. data() {
  61. return {
  62. fieldsMap,
  63. query: {
  64. // dimension: "hour",
  65. appid: '',
  66. platform_id: '',
  67. uni_platform: '',
  68. version_id: '',
  69. channel_id: '',
  70. start_time: [],
  71. },
  72. loading: false,
  73. currentDateTab: 1,
  74. tableData: [],
  75. panelData: fieldsMap.filter(f => f.hasOwnProperty('value')),
  76. chartData: {},
  77. type: 'visit_depth_data',
  78. types: [{
  79. _id: 'visit_depth_data',
  80. name: '访问页数'
  81. }, {
  82. _id: 'duration_data',
  83. name: '访问时长'
  84. }],
  85. field: 'visit_devices',
  86. fields: [{
  87. _id: 'visit_devices',
  88. name: '访问人数'
  89. }, {
  90. _id: 'visit_times',
  91. name: '访问次数'
  92. }],
  93. options: {
  94. visit_depth_data: {
  95. prefix: 'p',
  96. title: '页',
  97. value: [1, 2, 3, 4, 5, 10]
  98. },
  99. duration_data: {
  100. prefix: 's',
  101. title: '秒',
  102. value: [0, 3, 6, 11, 21, 31, 51, 100]
  103. }
  104. },
  105. channelData: [],
  106. errorMessage: "",
  107. }
  108. },
  109. computed: {
  110. fieldName() {
  111. return this.fields.forEach(item => {
  112. if (item._id === this.field) {
  113. return item.name
  114. }
  115. })
  116. },
  117. channelQuery() {
  118. const platform_id = this.query.platform_id
  119. return stringifyQuery({
  120. platform_id
  121. })
  122. },
  123. versionQuery() {
  124. const {
  125. appid,
  126. uni_platform
  127. } = this.query
  128. const query = stringifyQuery({
  129. appid,
  130. uni_platform,
  131. })
  132. return query
  133. }
  134. },
  135. created() {
  136. this.debounceGet = debounce(() => {
  137. this.getAllData(this.query);
  138. }, 300);
  139. this.getChannelData()
  140. },
  141. watch: {
  142. query: {
  143. deep: true,
  144. handler(val) {
  145. this.debounceGet()
  146. }
  147. },
  148. type() {
  149. this.getAllData(this.query)
  150. },
  151. field() {
  152. this.getAllData(this.query)
  153. }
  154. },
  155. methods: {
  156. useDatetimePicker() {
  157. this.currentDateTab = -1
  158. },
  159. changeAppid(id) {
  160. this.getChannelData(id, false)
  161. },
  162. changePlatform(id, index, name, item) {
  163. this.getChannelData(null, id)
  164. this.query.version_id = 0
  165. this.query.uni_platform = item.code
  166. },
  167. changeTimeRange(id, index) {
  168. this.currentDateTab = index
  169. const start = getTimeOfSomeDayAgo(id),
  170. end = getTimeOfSomeDayAgo(0) - 1
  171. this.query.start_time = [start, end]
  172. },
  173. // 此处 util 中的 groupField 不满足需求,特殊处理 groupField
  174. createStr(fields, type = "visit_depth_data") {
  175. const l = fields.length
  176. const p = this.options[type].prefix
  177. const value = this.options[type].value
  178. const strArr = value.map(item => {
  179. return fields.map(field => {
  180. return `sum(${type}.${field}.${p + '_' + item}) as ${l > 1 ? field + '_' + p +'_'+item : p + '_' + item}`
  181. })
  182. })
  183. const str = strArr.join()
  184. return str
  185. },
  186. parseChars(str) {
  187. str = str.split('_')
  188. const option = this.options[this.type]
  189. let chars = option.title
  190. const strArr = option.value.forEach((val, i) => {
  191. const next = option.value[i + 1]
  192. if (val === Number(str[str.length - 1])) {
  193. if (!next) {
  194. chars = val + '+' + chars
  195. } else if (val + 1 === next) {
  196. chars = val + chars
  197. } else {
  198. chars = val + '-' + (next - 1) + chars
  199. }
  200. }
  201. })
  202. return chars
  203. },
  204. getAllData(query) {
  205. if (!query.appid) {
  206. this.errorMessage = "请先选择应用";
  207. return; // 如果appid为空,则不进行查询
  208. }
  209. this.errorMessage = "";
  210. this.getChartData(query, this.field, this.fieldName)
  211. this.getTabelData(query)
  212. },
  213. getChartData(query, field = this.field, name = this.fields.find(f => f._id === this.field).name) {
  214. // this.chartData = {}
  215. query = stringifyQuery(query, null, ['uni_platform'])
  216. const groupField = this.createStr([field], this.type)
  217. const db = uniCloud.database()
  218. db.collection('uni-stat-loyalty-result')
  219. .where(query)
  220. .groupBy('appid')
  221. .groupField(groupField)
  222. .orderBy('start_time', 'asc')
  223. .get({
  224. getCount: true
  225. })
  226. .then(res => {
  227. let {
  228. count,
  229. data
  230. } = res.result
  231. data = data[0]
  232. const options = {
  233. series: [{
  234. data: [],
  235. }]
  236. }
  237. for (const key in data) {
  238. if (key !== 'appid') {
  239. const x = this.parseChars(key)
  240. const y = data[key]
  241. options.series[0].data.push({
  242. name: x,
  243. value: y
  244. })
  245. }
  246. }
  247. this.chartData = options
  248. }).catch((err) => {
  249. console.error(err)
  250. // err.message 错误信息
  251. // err.code 错误码
  252. }).finally(() => {
  253. this.loading = false
  254. })
  255. },
  256. getTabelData(query) {
  257. query = stringifyQuery(query, null, ['uni_platform'])
  258. const groupField = this.createStr(['visit_devices', 'visit_times'], this.type)
  259. this.fieldsMap[0].title = this.types.find(t => t._id === this.type).name
  260. this.loading = true
  261. const db = uniCloud.database()
  262. db.collection('uni-stat-loyalty-result')
  263. .where(query)
  264. .groupBy('appid')
  265. .groupField(groupField)
  266. .orderBy('start_time', 'asc')
  267. .get({
  268. getCount: true
  269. })
  270. .then(res => {
  271. const {
  272. count,
  273. data
  274. } = res.result
  275. const type = this.type
  276. const rows = []
  277. let splitor = this.options[type].prefix
  278. splitor = `_${splitor}_`
  279. for (const item of data) {
  280. for (const key in item) {
  281. if (key !== 'appid') {
  282. const row = {}
  283. const keys = key.split(splitor)
  284. row.name = keys[1]
  285. row[keys[0]] = item[key]
  286. rows.push(row)
  287. }
  288. }
  289. }
  290. const tableData = []
  291. // 归并得出访问人数 users、访问次数 times 的总和,用于计算占比
  292. const total = {}
  293. const reducer = (previousValue, currentValue) => previousValue + currentValue;
  294. let users = rows.filter(row => row.visit_devices)
  295. .map(row => row.visit_devices)
  296. users = users.length ? users.reduce(reducer) : 0
  297. let times = rows.filter(row => row.visit_times)
  298. .map(row => row.visit_times)
  299. times = times.length ? times.reduce(reducer) : 0
  300. total.visit_times = times
  301. total.visit_devices = users
  302. this.options[type].value.forEach(val => {
  303. const item = {}
  304. item.name = val + 'p'
  305. rows.forEach(row => {
  306. if (Number(row.name) === val) {
  307. for (const key in row) {
  308. if (key !== name) {
  309. item[key] = row[key]
  310. item['total_' + key] = total[key]
  311. }
  312. }
  313. }
  314. })
  315. item.name = this.parseChars(String(val))
  316. tableData.push(item)
  317. })
  318. for (const item of tableData) {
  319. mapfields(fieldsMap, item, item)
  320. }
  321. // this.options.total = count
  322. this.tableData = []
  323. this.tableData = tableData
  324. }).catch((err) => {
  325. console.error(err)
  326. // err.message 错误信息
  327. // err.code 错误码
  328. }).finally(() => {
  329. this.loading = false
  330. })
  331. },
  332. //获取渠道信息
  333. getChannelData(appid, platform_id) {
  334. this.query.channel_id = ''
  335. const db = uniCloud.database()
  336. const condition = {}
  337. //对应应用
  338. appid = appid ? appid : this.query.appid
  339. if (appid) {
  340. condition.appid = appid
  341. }
  342. //对应平台
  343. platform_id = platform_id ? platform_id : this.query.platform_id
  344. if (platform_id) {
  345. condition.platform_id = platform_id
  346. }
  347. let platformTemp = db.collection('uni-stat-app-platforms')
  348. .field('_id, name')
  349. .getTemp()
  350. let channelTemp = db.collection('uni-stat-app-channels')
  351. .where(condition)
  352. .field('_id, channel_name, create_time, platform_id')
  353. .getTemp()
  354. db.collection(channelTemp, platformTemp)
  355. .orderBy('platform_id', 'asc')
  356. .get()
  357. .then(res => {
  358. let data = res.result.data
  359. let channels = []
  360. if (data.length > 0) {
  361. let channelName
  362. for (let i in data) {
  363. channelName = data[i].channel_name ? data[i].channel_name : '默认'
  364. if (data[i].platform_id.length > 0) {
  365. channelName = data[i].platform_id[0].name + '-' + channelName
  366. }
  367. channels.push({
  368. value: data[i]._id,
  369. text: channelName
  370. })
  371. }
  372. }
  373. this.channelData = channels
  374. })
  375. .catch((err) => {
  376. console.error(err)
  377. // err.message 错误信息
  378. // err.code 错误码
  379. }).finally(() => {})
  380. }
  381. }
  382. }
  383. </script>
  384. <style lang="scss">
  385. .flex {
  386. display: flex;
  387. flex-wrap: wrap;
  388. align-items: center;
  389. }
  390. .label-text {
  391. font-size: 14px;
  392. color: #666;
  393. margin: auto 0;
  394. margin-right: 5px;
  395. }
  396. .line-bottom {
  397. border-bottom: 2px solid #eee;
  398. }
  399. .uni-stat-panel {
  400. box-shadow: unset;
  401. border-bottom: 1px solid #eee;
  402. padding: 0;
  403. margin: 0 15px;
  404. }
  405. </style>