stickiness.vue 11 KB

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