Compare commits

..

12 Commits

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 KiB

3
src/.gitignore vendored

@ -1,2 +1,3 @@
/node_modules /node_modules
/unpackage/dist /unpackage/dist
/.hbuilderx

@ -8,7 +8,7 @@
}, },
"mp-weixin" : "mp-weixin" :
{ {
"launchtype" : "local" "launchtype" : "remote"
}, },
"type" : "uniCloud" "type" : "uniCloud"
} }

@ -0,0 +1,66 @@
<template>
<view class="dishes-item">
<!-- 菜品左侧图片区域 -->
<view class="dishes-item-left">
<image :src="dishes.dish_src" class="dishes-pic"></image>
</view>
<!-- 菜品右侧信息区域 -->
<view class="dishes-item-right">
<!-- 菜品标题 -->
<view class="dishes-name">{{dishes.dish_name}}</view>
<view class="dishes-info-box">
</view>
</view>
</view>
</template>
<script>
export default {
props: {
dishes: {
type: Object,
default:{},
}
},
data: {
}
}
</script>
<style lang="scss">
.dishes-item {
display: flex;
padding: 10px 5px;
border-bottom: 1px solid #f0f0f0;
.dishes-item-left {
margin-right: 5px;
.dishes-pic {
width: 100px;
height: 100px;
display: block;
}
}
.dishes-item-right {
display: flex;
flex-direction: column;
justify-content: space-between;
.dishes-name {
font-size: 13px;
}
.dishes-price {
font-size: 16px;
color: #c00000;
}
}
}
</style>

@ -0,0 +1,119 @@
<template>
<view class="login-container">
<uni-icons type="contact-filled" size="100" color="#afafaf"></uni-icons>
<button type="primary" class="btn-login" @click="getUserInfo()"></button>
<text class="login-text">登陆后尽享更多权益</text>
<image :src="headerURL"></image>
</view>
</template>
<script>
import {
mapMutations
} from 'vuex'
export default {
name: "my-login",
data() {
return {
nickName: '',
headerURL: '',
};
},
methods: {
...mapMutations('m_user', ['updateUserInfo', 'updatetoken','savetokentostorage','saveUserInfoToStorage']),
getUserInfo() {
let that = this
uni.showModal({
title: '温馨提示',
content: '授权后使用',
success: function(res) {
if (res.confirm) {
uni.getUserProfile({
desc: '获取你的昵称头像',
success: userRes => {
if (userRes.userInfo !==
undefined) {
let userInfo = {
avatarURL: userRes.userInfo.avatarUrl,
nickName: userRes.userInfo.nickName
}
that.nickName = userRes.userInfo.nickName
console.log(userInfo)
that.getOpenID(userInfo)
} else {
uni.showToast({
icon: 'none',
title: '获取失败,请重试'
})
}
},
fail: error => {}
})
} else if (res.cancel) {}
},
fail: error => {
}
})
},
async getOpenID(userInfo) {
let that = this
uni.login({
provider: 'weixin',
success: function(loginAuth) {
let code = loginAuth.code
uniCloud.callFunction({
name: 'wxlogin',
data: {
code: code
},
success: function(res) {
that.updatetoken(res)
that.savetokentostorage()
that.updateUserInfo(userInfo)
that.saveUserInfoToStorage()
}
})
}
})
console.log(userInfo)
}
},
}
</script>
<style lang="scss">
.login-container {
height: 750rpx;
background-color: #f8f8f8;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
&::after {
content: '';
display: block;
width: 100%;
height: 40px;
background-color: #f8f8f8;
position: absolute;
bottom: 0;
left: 0;
border-radius: 100%;
transform: translateY(50%);
}
.btn-login {
width: 90%;
border-radius: 100px;
margin: 15px 0;
background-color: #C00000;
}
}
</style>

@ -0,0 +1,64 @@
<template>
<view class="my-search-container" @click="searchBoxHandler">
<!-- 使用 view 组件模拟 input 输入框的样式 -->
<view class="my-search-box">
<uni-icons type="search" size="17"></uni-icons>
<text class="placeholder">搜索</text>
</view>
</view>
</template>
<script>
export default {
name:"my-search",
props: {
//
bgcolor: {
type: String,
default: '#C00000'
},
//
radius: {
type: Number,
// px
default: 18
}
},
data() {
return {
};
},
methods: {
searchBoxHandler(){
// @click click
this.$emit('click')
}
}
}
</script>
<style lang="scss">
.my-search-container {
background-color: #c00000;
height: 50px;
padding: 0 10px;
display: flex;
align-items: center;
}
.my-search-box {
height: 36px;
background-color: #ffffff;
border-radius: 15px;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
.placeholder {
font-size: 15px;
margin-left: 5px;
}
}
</style>

@ -0,0 +1,171 @@
<template>
<view class="my-userinfo-container">
<!--头像和昵称区域-->
<view class="top-box">
<image :src="userinfo.avatarURL" class="avatar"></image>
<button class="header" open-type="chooseAvatar" @chooseavatar="chooseAvatar"></button>
</view>
<!--面版区域-->
<view class="panel-list">
<!--第一个面板-->
<view class="panel">
<view class="panel-body">
<view class="panel-item">
<text>8</text>
<text>收藏的菜品</text>
</view>
<view class="panel-item">
<text>2</text>
<text>信息</text>
</view>
<view class="panel-item">
<text>6</text>
<text>足迹</text>
</view>
</view>
</view>
<!--第二个面板-->
<view class="panel">
<view class="panel-title">
我的评价
</view>
<view class="panel-body">
</view>
</view>
<!--第三个面板-->
<view class="panel">
<view class="third-panel-list">
<text>反馈与帮助</text>
<uni-icons type="arrowright" size="15"></uni-icons>
</view>
<view class="third-panel-list" @click="logout">
<text>退出登录</text>
<uni-icons type="arrowright" size="15"></uni-icons>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from 'vuex'
export default {
name: "my-userinfo",
data() {
return {
};
},
onLoad() {
this.updateUserInfo(uni.getStorageInfoSync('userinfo'))
},
computed: {
...mapState('m_user', ['userinfo'])
},
methods: {
...mapMutations('m_user', ['updatetoken', 'updateUserInfo','saveUserInfoToStorage']),
chooseAvatar(e) {
this.userinfo.avatarURL = e.detail.avatarUrl
this.saveUserInfoToStorage()
},
async logout() {
let that = this
uni.showModal({
title: '提示',
content: '确认退出登录吗?',
success: (succ) => {
if (succ.confirm) {
that.updatetoken('')
that.savetokentostorage()
}
}
})
}
}
}
</script>
<style lang="scss">
.my-userinfo-container {
height: 100%;
background-color: #f4f4f4;
.top-box {
height: 400rpx;
background-color: #efefef;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
.avatar {
width: 90px;
height: 90px;
border-radius: 45px;
border: 2px solid #FFF;
box-shadow: 0 1px 5px black;
}
.nikname {
font-size: 16px;
color: black;
font-weight: bold;
margin-top: 10px;
}
}
}
.panel-list {
padding: 0 10px;
position: relative;
top: -10px;
.panel {
background-color: white;
border-radius: 3px;
margin-bottom: 8px;
.panel-title {
line-height: 45px;
padding-left: 10px;
font-size: 15px;
border-bottom: 1px solid #f8f8f8;
}
.panel-body {
display: flex;
justify-content: space-around;
.panel-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
padding: 10px 0;
font-size: 13px;
.icon {
width: 35px;
height: 35px;
}
}
}
}
}
.third-panel-list {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 18px;
padding: 0 10px;
line-height: 45px;
}
</style>

@ -5,12 +5,30 @@ import Vue from 'vue'
import './uni.promisify.adaptor' import './uni.promisify.adaptor'
Vue.config.productionTip = false Vue.config.productionTip = false
App.mpType = 'app' App.mpType = 'app'
import store from '@/store/store.js'
const app = new Vue({ const app = new Vue({
...App ...App,
store
}) })
app.$mount() app.$mount()
// #endif // #endif
import { $http } from '@escook/request-miniprogram'
uni.$http = $http
// 配置请求根路径
$http.baseUrl = ''
// 请求开始之前做一些事情
$http.beforeRequest = function (options) {
uni.showLoading({
title: '数据加载中...',
})
}
// 请求完成之后做一些事情
$http.afterRequest = function () {
uni.hideLoading()
}
// #ifdef VUE3 // #ifdef VUE3
import { createSSRApp } from 'vue' import { createSSRApp } from 'vue'
export function createApp() { export function createApp() {

@ -50,9 +50,10 @@
"quickapp" : {}, "quickapp" : {},
/* */ /* */
"mp-weixin" : { "mp-weixin" : {
"appid" : "", "appid" : "wxcfebd67cf993c6f3",
"setting" : { "setting" : {
"urlCheck" : false "urlCheck" : false,
"es6" : true
}, },
"usingComponents" : true "usingComponents" : true
}, },

543
src/package-lock.json generated

@ -0,0 +1,543 @@
{
"name": "canteen",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "canteen",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@escook/request-miniprogram": "^0.2.1",
"request-promise": "^4.2.6"
}
},
"node_modules/@escook/request-miniprogram": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@escook/request-miniprogram/-/request-miniprogram-0.2.1.tgz",
"integrity": "sha512-ueWV5YsaEm/ycQZuEjMiA88GFMhfBQSjy9GrP9omy4xAQajkGTbYIlnhzsDfWzRPmRC1fKmAiKMrCVcgS+SHcQ=="
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"peer": true,
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
"peer": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"peer": true
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/aws4": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz",
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==",
"peer": true
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"peer": true,
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"peer": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"peer": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
"peer": true
},
"node_modules/dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
"peer": true,
"dependencies": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"peer": true
},
"node_modules/extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
"engines": [
"node >=0.6.0"
],
"peer": true
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"peer": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"peer": true
},
"node_modules/forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"peer": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0"
}
},
"node_modules/har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==",
"peer": true,
"engines": {
"node": ">=4"
}
},
"node_modules/har-validator": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
"integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
"deprecated": "this library is no longer supported",
"peer": true,
"dependencies": {
"ajv": "^6.12.3",
"har-schema": "^2.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==",
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
},
"engines": {
"node": ">=0.8",
"npm": ">=1.3.7"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"peer": true
},
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
"peer": true
},
"node_modules/jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"peer": true
},
"node_modules/json-schema": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
"peer": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"peer": true
},
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
"peer": true
},
"node_modules/jsprim": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
"integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==",
"peer": true,
"dependencies": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.4.0",
"verror": "1.10.0"
},
"engines": {
"node": ">=0.6.0"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"peer": true
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
"integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==",
"peer": true,
"engines": {
"node": ">=0.6"
}
},
"node_modules/request": {
"version": "2.88.2",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
"deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
"peer": true,
"dependencies": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.6",
"extend": "~3.0.2",
"forever-agent": "~0.6.1",
"form-data": "~2.3.2",
"har-validator": "~5.1.3",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19",
"oauth-sign": "~0.9.0",
"performance-now": "^2.1.0",
"qs": "~6.5.2",
"safe-buffer": "^5.1.2",
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/request-promise": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.6.tgz",
"integrity": "sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==",
"deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
"dependencies": {
"bluebird": "^3.5.0",
"request-promise-core": "1.1.4",
"stealthy-require": "^1.1.1",
"tough-cookie": "^2.3.3"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"request": "^2.34"
}
},
"node_modules/request-promise-core": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
"integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
"dependencies": {
"lodash": "^4.17.19"
},
"engines": {
"node": ">=0.10.0"
},
"peerDependencies": {
"request": "^2.34"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"peer": true
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"peer": true
},
"node_modules/sshpk": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz",
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==",
"peer": true,
"dependencies": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"safer-buffer": "^2.0.2",
"tweetnacl": "~0.14.0"
},
"bin": {
"sshpk-conv": "bin/sshpk-conv",
"sshpk-sign": "bin/sshpk-sign",
"sshpk-verify": "bin/sshpk-verify"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stealthy-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
"integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dependencies": {
"psl": "^1.1.28",
"punycode": "^2.1.1"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"peer": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"peer": true
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"peer": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"peer": true,
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
"engines": [
"node >=0.6.0"
],
"peer": true,
"dependencies": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "^1.2.0"
}
}
}
}

@ -0,0 +1,16 @@
{
"name": "canteen",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@escook/request-miniprogram": "^0.2.1",
"request-promise": "^4.2.6"
}
}

@ -1,53 +1,111 @@
{ {
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages "pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{ {
"path": "pages/index/index", "path": "pages/home/home",
"style": { "style": {
"navigationBarTitleText": "uni-app" "navigationBarTitleText": "菜品圈",
"enablePullDownRefresh": true
} }
}, {
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
}, {
"path": "pages/my/my",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
}, {
"path": "pages/ranking/ranking",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": true
}
} }
,{ ,{
"path" : "pages/home/home", "path" : "pages/recommend/recommend",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/search/search",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/my/my",
"style" : "style" :
{ {
"navigationBarTitleText": "", "navigationBarTitleText": "",
"enablePullDownRefresh": false "enablePullDownRefresh": true
}
}
,{
"path" : "pages/ranking/ranking",
"style" :
{
"navigationBarTitleText": "",
"enablePullDownRefresh": false
} }
} }
], ],
"globalStyle": { "globalStyle": {
"navigationBarTextStyle": "black", "navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app", "navigationBarTitleText": "菜品圈",
"navigationBarBackgroundColor": "#F8F8F8", "navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8" "backgroundColor": "#F8F8F8"
}, },
"uniIdRouter": {} "subpackages": [{
} "root": "subpkg",
"pages": [{
"path": "dishDetail/dishDetail",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}, {
"path": "search/search",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
},
{
"path": "searchList/searchList",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
]
}],
"tabBar": {
"color": "#a9a9a9",
"selectedColor": "#000000",
"list": [{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "static/static/images/home/home1.png",
"selectedIconPath": "static/static/images/home/home2.png"
},
{
"pagePath": "pages/search/search",
"text": "筛选",
"iconPath": "static/static/images/home/筛选2.png",
"selectedIconPath": "static/static/images/home/筛选1.png"
},
{
"pagePath": "pages/recommend/recommend",
"text": "推荐",
"iconPath": "static/static/images/home/推荐1.png",
"selectedIconPath": "static/static/images/home/推荐2.png"
},
{
"pagePath": "pages/ranking/ranking",
"text": "排行榜",
"iconPath": "static/static/images/home/排行榜2.png",
"selectedIconPath": "static/static/images/home/排行榜1.png"
},
{
"pagePath": "pages/my/my",
"text": "个人中心",
"iconPath": "/static/static/images/home/我的1.png",
"selectedIconPath": "/static/static/images/home/我的2.png"
}
]
}
}

@ -1,19 +1,146 @@
<template> <template>
<view> <view>
<view class="search-box">
<my-search @click="gotoSearch"></my-search>
</view>
<swiper :class="swiper" :indicator-dots="true " :autoplay="true" :interval="3000" :duration="1000"
:circular="true">
<swiper-item v-for="(item,i) in swiperList" :key="i">
<image class="image" :src="item.image_src"></image>
</swiper-item>
</swiper>
<view class="dishes-list">
<view v-for="(dishes, i) in floorList" :key="i" @click="gotoDetail(dishes)">
<view class=" dishes-item">
<!-- 菜品左侧图片区域 -->
<view class="dishes-item-left">
<image :src="dishes.dish_src" class="dishes-pic"></image>
</view>
<!-- 菜品右侧信息区域 -->
<view class="dishes-item-right">
<!-- 菜品标题 -->
<view class="dishes-name">{{dishes.dish_name}}</view>
<view class="dishes-info-box">
{{dishes.location}}
</view>
</view>
</view>
</view>
</view>
<m-tabbar native=""></m-tabbar>
</view> </view>
</template> </template>
<script> <script>
import {
mapState,
mapActions
} from "vuex";
export default { export default {
name: "swiper-index",
data() { data() {
return { return {
swiperList: [],
floorList: []
}; };
},
onLoad() {
this.getSwiperList()
this.getFloorList()
const sysInfo = uni.getSystemInfoSync()
// = - navigationBar - tabBar - search
this.wh = sysInfo.windowHeight - 50
},
methods: {
gotoSearch() {
uni.navigateTo({
url: '/subpkg/search/search'
})
},
gotoDetail(dishes) {
uni.navigateTo({
url:'/subpkg/dishDetail/dishDetail?_id='+encodeURIComponent(JSON.stringify(dishes._id))
})
},
getSwiperList() {
let that = this
uniCloud.callFunction({
name: "getSwiperImage",
data: "",
success: function(res) {
that.swiperList = res.result
console.log(res)
}
})
},
getFloorList() {
let that = this
uniCloud.callFunction({
name: "getDishes",
data: {
api: "getFloorList"
},
success: function(res) {
that.floorList = res.result.data
console.log(res)
}
})
},
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.swiper {
height: 50rpx;
z-index: -1;
}
.image {
width: 100%;
height: 100%;
}
.dishes-item {
display: flex;
padding: 10px 5px;
border-bottom: 1px solid #f0f0f0;
}
.dishes-item-left {
margin-right: 5px;
}
.dishes-pic {
width: 100px;
height: 100px;
display: block;
}
.dishes-item-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dishes-name {
font-size: 13px;
}
.dishes-price {
font-size: 16px;
color: #c00000;
}
.search-box {
//
position: sticky;
//
top: 0;
//
z-index: 999;
}
</style>
</style>

@ -1,19 +1,39 @@
<template> <template>
<view> <view class="my-container">
<my-login v-if="!token"></my-login>
</view>
<my-userinfo v-else></my-userinfo>
<m-tabbar native=""></m-tabbar>
</view>
</template> </template>
<script> <script>
export default { import {mapState} from "vuex"
data() { import {
return { mapMutations
} from 'vuex'
}; export default {
} data() {
} return {
};
},
onLoad(){
this.updatetoken(uni.getStorageSync('token'))
this.savetokentostorage()
},
methods:{
...mapMutations('m_user', ['updateUserInfo', 'updatetoken','savetokentostorage'])
},
computed: {
...mapState('m_user',['token'])
}
}
</script> </script>
<style lang="scss"> <style lang="scss">
page,
.my-container {
height: 100%;
}
</style> </style>

@ -1,19 +1,214 @@
<template> <template>
<view> <view>
<view class="ranking">
<view class="ranking-name">
<scroll-view class="left-scroll-view" scroll-y="true" :style="{height: wh+'px'}">
<view v-for="(labels, i) in labelNames" :key="i">
<view :class="['left-scroll-view-item', i === active ? 'active' : '']" @click="activeChange(i)">
{{labels.label_name}}
</view>
</view>
</scroll-view>
</view>
<view class="ranking-content">
<view v-for="(dishes,i) in labeldishes" :key="i" @click="gotoDetail(dishes)">
<view class=" dishes-item">
<!-- 菜品左侧图片区域 -->
<view class="dishes-item-left">
<image :src="dishes.dish_src" class="dishes-pic"></image>
</view>
<!-- 菜品右侧信息区域 -->
<view class="dishes-item-right">
<view class="dishes-name">{{dishes.dish_name}}</view>
<view class="dishes-score">{{dishes.avg_score}}</view>
</view>
</view>
</view>
</view>
</view>
<m-tabbar native=""></m-tabbar>
</view> </view>
</template> </template>
<script> <script>
export default { export default{
data() { data(){
return { return{
labelNames: [],
active:0,
//
labeldishes:[],
currentLabel:'面'
}; };
} },
onLoad(){
this.getLabels()
// this.getdata()
const sysInfo = uni.getSystemInfoSync()
// = - navigationBar - tabBar - search
this.wh = sysInfo.windowHeight - 50
this.getDishes()
},
methods : {
activeChange(i) {
const { label_name } = this.labelNames[i]; // labelNames[i]label_name
this.active = i;
this.currentLabel = label_name; // label_namecurrentLabel
this.getDishes(); // getDishes()
},
async getLabels() {
let that = this
uniCloud.callFunction({
name: "getLabels",
data: null,
success: function(res) {
that.labelNames = res.result.data
}
});
},
async getDishes(){
let that = this
uniCloud.callFunction({
name: "getDishes",
data: {
api: "getByLabels",
labels:[that.currentLabel]
},
success: function(res) {
if (Array.isArray(res.result)) {
that.labeldishes = res.result
.filter(dish => dish !== null)
.sort((a, b) => b.avg_score - a.avg_score);
} else {
that.labeldishes = [];
}
},
})
},
gotoDetail(dishes) {
uni.navigateTo({
url:'/subpkg/dishDetail/dishDetail?_id='+encodeURIComponent(JSON.stringify(dishes._id))
})
},
},
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.ranking-name {
display: flex;
flex-direction: column;
}
.title {
background-color: white;
padding: 10px;
text-align: center;
}
.image {
width: 100%;
height: 100%;
}
.ranking {
display: flex;
}
.ranking-name {
flex-basis: 20%;
background-color: lightgray;
padding: 10px;
}
.ranking-content {
flex-grow: 1;
padding: 10px;
}
.dishes-item {
display: flex;
padding: 10px 5px;
border-bottom: 1px solid #f0f0f0;
}
.dishes-item-left {
margin-right: 5px;
}
.dishes-pic {
width: 100px;
height: 100px;
display: block;
}
</style> .dishes-item-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dishes-name {
font-size: 13px;
}
.image {
width: 100%;
height: 100%;
}
.dishes-item {
display: flex;
padding: 10px 5px;
border-bottom: 1px solid #f0f0f0;
}
.dishes-item-left {
margin-right: 5px;
}
.dishes-pic {
width: 100px;
height: 100px;
display: block;
}
.dishes-item-right {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dishes-name {
font-size: 13px;
}
.scroll-view-dish{
display: flex;
}
.left-scroll-view{
//width: 120px;
.left-scroll-view-item{
background-color: #f7f7f7;
line-height: 60PX;
text-align: center;
font-size: 12px;
&.active{
background-color: #ffffff;
position: relative;
&::before{
content: '';
display: block;
width: 3px;
height: 30px;
background-color: #c00000;
position: absolute;
top: 50%;
left:0;
transform: translateY(-50%);
}
}
}
}
.dishes-lv2{
font-size: 12px;
font-weight: bold;
text-align: center;
padding: 15px 0;
}
</style>

@ -0,0 +1,138 @@
<template>
<view>
<view class="dish-list">
<view class="product-list">
<view class="dish" v-for="(dishes, i) in dishList" :key="i" @click="gotoDetail(dishes)">
<image class="image" v-if="dishes" mode="widthFix" :src="dishes.dish_src"></image>
<view class="name">{{dishes.dish_name}}</view>
</view>
</view>
<view class="loading-text">{{}}</view>
</view>
<m-tabbar native=""></m-tabbar>
</view>
</template>
<script>
import {
mapState
} from "vuex"
import {
mapMutations
} from 'vuex'
export default {
data() {
return {
user_id:'',
labelList:[],
dishList:[]
}
},
onLoad(){
let that = this
this.getUserID()
},
methods: {
gotoDetail(item) {
console.log(item._id)
uni.navigateTo({
url: '/subpkg/dishDetail/dishDetail?_id=' + encodeURIComponent(JSON.stringify(item._id))
})
},
async getDishes() {
let that = this
uniCloud.callFunction({
name: "getDishes",
data: {
api: "getByLabels",
labels: that.labelList
},
success: function(res) {
console.log(res)
that.dishList = res.result
}
})
},
async getRecommend() {
let that = this
uniCloud.callFunction({
name:'recommendService',
data:{
token:that.user_id
},
success: (res) => {
that.labelList = res.result
that.getDishes()
}
})
},
async getUserID(){
let that = this
uniCloud.callFunction({
name:'getUser',
data:that.token,
success(res) {
that.user_id = res.result.data[0]._id
that.getRecommend()
}
})
}
},
computed: {
...mapState('m_user', ['token'])
}
}
</script>
<style scoped lang="scss">
.dish-list {
padding-top: 10px;
}
.loading-text {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
color: #979797;
font-size: 12px;
}
.product-list {
width: 92%;
padding: 0 4% 3vw 4%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.product {
width: 48%;
border-radius: 10px;
background-color: #fff;
margin: 0 0 7px 0;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.1);
}
.image {
width: 100%;
width: 150px;
height: 150px;
border-radius: 0px 10px 0 0;
}
.name {
width: 92%;
padding: 5px 4%;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: justify;
overflow: hidden;
font-size: 15px;
}
</style>

@ -1,19 +1,201 @@
<template> <template>
<view> <view>
<button style="margin: 60px 90px;" @click="showPopClick"></button>
<!-- pop-show:是否显示弹框 color:主题色 classList分类数组 @changeClick数据发生变化 @sureClick筛选确认 @hideClick隐藏事件 -->
<cc-rightPopup :pop-show="popShow" :colors="colors" :classList="classList" @change="changeClick"
@sureClick="sureClick" @hideClick="hideright" @getDishes="getDishes"></cc-rightPopup>
<view class="dish-list">
<view class="product-list">
<view class="dish" v-for="(dishes, i) in dishList" :key="i" @click="gotoDetail(dishes)">
<image class="image" v-if="dishes" mode="widthFix" :src="dishes.dish_src"></image>
<view class="name">{{dishes.dish_name}}</view>
</view>
</view>
<view class="loading-text">{{}}</view>
</view>
<m-tabbar native=""></m-tabbar>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
colors: '#fa436a',
}; popShow: false,
labelList: [],
dishList: [],
classList: [{
name: '菜品标签',
id: 1,
child: [{
label: "面",
id: 10,
current: false
}, {
label: "鸡肉",
id: 11,
current: false
}, {
label: "牛肉",
id: 12,
current: false
}, {
label: "米饭",
id: 13,
current: false
}, {
label: "鸭肉",
id: 14,
current: false
},{
label: "麻辣烫",
id: 15,
current: false
},{
label: "早点",
id: 16,
current: false
}]
}]
}
},
onLoad() {
const sysInfo = uni.getSystemInfoSync()
// = - navigationBar - tabBar - search
this.wh = sysInfo.windowHeight - 50
},
methods: {
gotoDetail(item) {
console.log(item._id)
uni.navigateTo({
url: '/subpkg/dishDetail/dishDetail?_id=' + encodeURIComponent(JSON.stringify(item._id))
})
},
getDishes() {
let that = this
uniCloud.callFunction({
name: "getDishes",
data: {
api: "getByLabels",
labels: that.labelList
},
success: function(res) {
console.log(res)
that.dishList = res.result
}
})
},
changeClick(arr) {
//
this.classList = arr;
},
showPopClick() {
this.popShow = true;
},
hideright() {
this.popShow = true;
},
sureClick(paramDict, selArr) {
this.labelList = []
this.classList.forEach(item => {
item.child.forEach(ele => {
if (ele.current == true) {
this.labelList.push(ele.label)
}
});
})
this.popShow = false;
},
} }
} }
</script> </script>
<style lang="scss">
</style> <style>
.dish-list {
padding-top: 10px;
}
.loading-text {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 30px;
color: #979797;
font-size: 12px;
}
.product-list {
width: 92%;
padding: 0 4% 3vw 4%;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
.product {
width: 48%;
border-radius: 10px;
background-color: #fff;
margin: 0 0 7px 0;
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.1);
}
.image {
width: 100%;
width: 150px;
height: 150px;
border-radius: 0px 10px 0 0;
}
.name {
width: 92%;
padding: 5px 4%;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
text-align: justify;
overflow: hidden;
font-size: 15px;
}
.info {
display: flex;
justify-content: space-between;
align-items: flex-end;
width: 92%;
padding: 5px 4% 5px 4%;
}
.price {
color: #e65339;
font-size: 15px;
font-weight: 600;
}
.label {
color: #807c87;
font-size: 12px;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@ -0,0 +1,12 @@
import Vue from 'vue'
import Vuex from 'vuex'
import moduleUser from './user.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
'm_user': moduleUser
}
})
export default store

@ -0,0 +1,35 @@
export default {
//开启命名空间
namespaced: true ,
//数据
state: () => ({
token : '',
//用户的信息对象
userinfo: JSON.parse(uni.getStorageSync('userinfo') || '{}')
}),
//方法
mutations : {
updateUserInfo(state,userinfo){
state.userinfo = userinfo
this.commit('m_user/saveUserInfoToStorage')
},
saveUserInfoToStorage(state)
{
uni.setStorageSync('userinfo',JSON.stringify(state.userinfo))
},
updatetoken(state, token)
{
state.token = token
this.commit('m_user/savetokentostorage')
},
savetokentostorage(state){
uni.setStorageSync('token',state.token)
}
},
getters: {},
}

@ -0,0 +1,172 @@
<template>
<view>
<view class="image_view">
<image class='dish_image' :src="dish_image_src"></image>
</view>
<view>
<text>{{"菜品名称:" + dish_name + '\n'}}</text>
<text>{{"菜品位置:" + dish_location + dish_window_name + '\n'}}</text>
<text>{{"菜品类别:" + JSON.stringify(dish_label) + '\n'}}</text>
<text>{{"菜品评分:" + dish_avg_score + '\n'}}</text>
</view>
<view>
<text>{{"菜品打分:\n"}}</text>
<uni-rate v-model="value" @change="onChange" allow-half="true" size="75" />
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex"
import {
mapMutations
} from 'vuex'
export default {
data() {
return {
dish_image_src: '',
dish_name: '',
dish_label: [],
dish_location: '',
dish_window_name: '',
value: 4,
comentData: [],
dish_id: '',
dish_avg_score: 5.0,
dish_label_id: [0, 0, 0, 0, 0, 0, 0],
user_id:''
};
},
onLoad(option) {
let that = this
//this.getLabels()
this.dish_id = JSON.parse(decodeURIComponent(option._id))
this.getUserID()
this.getImage()
this.getLabels()
console.log(this.token)
console.log(this.dish_id)
},
methods: {
async getUserID(){
let that = this
uniCloud.callFunction({
name:'getUser',
data:that.token,
success(res) {
that.user_id = res.result.data[0]._id
}
})
}
,
async recommendData() {
let that = this
if (this.token) {
uniCloud.callFunction({
name: 'recommendDataUpdate',
data: {
token: that.user_id,
labels: that.dish_label_id
},
success:function(res){
console.log(res)
}
})
}
},
async getImage() {
let that = this
uniCloud.callFunction({
name: 'getDishes',
data: {
api: 'getByID',
id: this.dish_id
},
success: function(res) {
that.dish_image_src = res.result.data[0].dish_src
that.dish_name = res.result.data[0].dish_name
that.dish_location = res.result.data[0].location
that.dish_window_name = res.result.data[0].window_name
that.dish_avg_score = res.result.data[0].avg_score
console.log(res)
}
})
},
async getLabels() {
let that = this
uniCloud.callFunction({
name: 'getlablelist',
data: {
id: that.dish_id
},
success: function(res) {
that.dish_label = res.result
for (let i = 0; i < that.dish_label.length; i++) {
console.log(that.dish_label[i])
if (that.dish_label[i] === "面") {
that.dish_label_id[0] = 1;
} else if (that.dish_label[i] === "鸡蛋") {
that.dish_label_id[1] = 1;
} else if (that.dish_label[i] === "牛肉") {
that.dish_label_id[2] = 1;
} else if (that.dish_label[i] === "米饭") {
that.dish_label_id[3] = 1;
} else if (that.dish_label[i] === "汤") {
that.dish_label_id[4] = 1;
} else if (that.dish_label[i] === "麻辣烫") {
that.dish_label_id[5] = 1;
} else if (that.dish_label[i] === "鸭肉") {
that.dish_label_id[6] = 1;
}
}
that.recommendData()
}
})
},
onChange(e) {
let that = this
uniCloud.callFunction({
name: 'scoreUpdate',
data: {
score: that.value,
dish_id: that.dish_id
}
})
},
...mapMutations('m_user', ['updateUserInfo', 'updatetoken', 'savetokentostorage'])
},
computed: {
...mapState('m_user', ['token'])
}
}
</script>
<style lang="scss">
.image_view {
height: 400rpx;
display: flex;
justify-content: center;
padding-left: 10rpx;
padding-right: 10rpx;
}
.dish_image {
display: flex;
height: 400rpx;
width: 600rpx;
}
.evaluete {}
</style>

@ -15,7 +15,10 @@
} }
}, },
onLoad() { onLoad() {
uniCloud.callFunction({
name:'getLabels',
data:''
}).then(res => console.log(res))
}, },
methods: { methods: {

@ -0,0 +1,192 @@
<template>
<view>
<view class="search-box">
<uni-search-bar @input="input" :radius="100" cancelButton="none" :focus="true"></uni-search-bar>
<button class="search" @click="goToSearchList"></button>
</view>
<!--搜索建议-->
<view class="sugest-list" v-if="searchresult.length !== 0">
<view class="sugest-item" v-for="(item,i) in searchresult" :key="i" @click="gotodetail(item)">
<view class="dish-name">{{item.dish_name}}</view>
<uni-icons type="arrowright" size="16"></uni-icons>
</view>
</view>
<!--搜索历史-->
<view class="history-box" v-else>
<!--标题区域-->
<view class="history-title">
<text>搜索历史</text>
<uni-icons type="trash" size="17" @click="clean"></uni-icons>
</view>
<!--列表区域-->
<view class="history-list">
<uni-tag v-for="(item,i) in histories" :text="item" :key="i" @click="gotosearchdetail(item)"></uni-tag>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
timer: null,
kw: '',
searchresult: [],
//
searchlist: [],
//
historylist: [],
};
},
onLoad() {
this.historylist = JSON.parse(uni.getStorageSync('kw') || '[]')
this.cnt = this.historylist.length
console.log(this.historylist)
},
methods: {
async goToSearchList() {
this.getsearchresult()
this.savesearchhistory()
let that = this
setTimeout(()=>{
uni.$emit('item',this.searchresult)
},500)
uni.navigateTo({
url:'/subpkg/searchList/searchList?searchlist=' + encodeURIComponent(JSON.stringify(this.searchresult))
})
},
//input
input(e) {
//
clearTimeout(this.timer)
this.timer = setTimeout(
() => {
this.kw = e
this.getsearchresult()
}, 500)
},
async getsearchresult() {
let that = this
//
if (this.kw.length === 0) {
this.searchresult = []
return
}
uniCloud.callFunction({
name:'getDishes',
data:{
api:'getByName',
dish_name:that.kw
},
success:function(res){
that.searchresult = res.result.data
that.searchlist = res.result.data
}
})
},
gotodetail(item) {
this.savesearchhistory()
console.log(item._id)
uni.navigateTo({
url: '/subpkg/dishDetail/dishDetail?_id=' +encodeURIComponent(JSON.stringify(item._id))
})
},
savesearchhistory() {
const set = new Set(this.historylist)
set.delete(this.kw)
set.add(this.kw)
this.historylist = Array.from(set)
uni.setStorageSync('kw', JSON.stringify(this.historylist))
},
//
clean() {
this.historylist = []
uni.setStorageSync('kw', '[]')
this.cnt = 0
},
gotosearchdetail(item) {
let historyresult = []
uniCloud.callFunction({
name:'getDishes',
data:{
api:'getByName',
dish_name:item
},
success:function(res){
historyresult = res.result.data
}
})
setTimeout(()=>{
uni.$emit('item',historyresult)
},500)
uni.navigateTo({
url:'/subpkg/searchList/searchList?searchlist=' + encodeURIComponent(JSON.stringify(historyresult))
})
}
},
computed: {
histories() {
return [...this.historylist].reverse()
}
}
}
</script>
<style lang="scss">
.search-box {
position: sticky;
top: 0;
z-index: 999;
}
.sugest-list {
padding: 0 5px;
.sugest-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
padding: 13px 0;
border-bottom: 1 px solid #efefef;
.dish-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.history-box {
padding: 0 5px;
.history-title {
display: flex;
justify-content: space-between;
height: 40px;
align-items: center;
font-size: 13px;
border-bottom: 1px solid #efefef;
}
.history-list {
display: flex;
flex-wrap: wrap;
.uni-tag {
margin-top: 5px;
margin-right: 5px;
}
}
}
.search{
height: 50rpx;
width: 200rpx;
font-size: 20rpx;
justify-content: center;
}
</style>

@ -0,0 +1,83 @@
<template>
<view>
<view class="search-list" v-for="data in dishList" :key="data._id">
<view class="dish-item" @click="goToDetail(data)">
<image class="dish-image" :src="data.dish_src"></image>
</view>
<view class="dish-intro">
<text>{{'菜名:' + data.dish_name + '\n'}}</text>
<text>{{'地点:' + data.location + '\n'}}</text>
<text>{{'窗口:' + data.window_name + '\n'}}</text>
<text>{{'评分:' + data.avg_score + '\n'}}</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
dishList: []
};
},
onLoad(e) {
let that = this
uni.$on('item', (res) => {
that.dishList = res
console.log(res)
})
//this.dishList = JSON.parse(decodeURIComponent(e.searchlist))
},
methods:{
goToDetail(item) {
console.log(item._id)
uni.navigateTo({
url: '/subpkg/dishDetail/dishDetail?_id=' +encodeURIComponent(JSON.stringify(item._id))
})
}
}
}
</script>
<style lang="scss">
.search-list {
padding-left: 5rpx;
padding-right: 5rpx;
border-style: solid;
border-radius: 5px;
border-color: #65654b;
border-width: 3rpx;
display: flex;
justify-content: space-around;
}
.dish-intro {
padding-left: 5rpx;
padding-right: 5rpx;
border-style: solid;
border-radius: 5px;
border-color: #65654b;
border-width: 3rpx;
display: flex;
flex-direction: column;
justify-content: center;
height: 256rpx;
font-size: 18rpx;
}
.dish-image {
padding-left: 5rpx;
padding-right: 5rpx;
border-style: solid;
border-radius: 5px;
border-color: #65654b;
border-width: 3rpx;
width: 475rpx;
height: 256rpx;
display: flex;
}
</style>

@ -0,0 +1,8 @@
'use strict';
exports.main = async (event, context) => {
//event为客户端上传的参数
const db = uniCloud.database()
const res = await db.collection('user-label').where({token:event.token}).get()
console.log(res.data[0].label_id[0])
return res
};

@ -0,0 +1,7 @@
{
"name": "ArrayTest",
"dependencies": {},
"extensions": {
"uni-cloud-jql": {}
}
}

@ -0,0 +1,23 @@
const {
createApi
} = require('./shared/index')
let reportDataReceiver, dataStatCron
module.exports = {
//uni统计数据上报数据接收器初始化
initReceiver: (options = {}) => {
if(!reportDataReceiver) {
reportDataReceiver = require('./stat/receiver')
}
options.clientType = options.clientType || __ctx__.PLATFORM
return createApi(reportDataReceiver, options)
},
//uni统计数据统计模块初始化
initStat: (options = {}) => {
if(!dataStatCron) {
dataStatCron = require('./stat/stat')
}
options.clientType = options.clientType || __ctx__.PLATFORM
return createApi(dataStatCron, options)
}
}

@ -0,0 +1,5 @@
{
"name": "uni-stat",
"version": "1.0.0",
"lockfileVersion": 1
}

@ -0,0 +1,15 @@
{
"name": "uni-stat",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"uni-config-center": "file:../../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}

@ -0,0 +1,82 @@
const {
isFn,
isPlainObject
} = require('./utils')
/**
* 实例参数处理注意不进行递归处理
* @param {Object} params 初始参数
* @param {Object} rule 规则集
* @returns {Object} 处理后的参数
*/
function parseParams (params = {}, rule) {
if (!rule || !params) {
return params
}
const internalKeys = ['_pre', '_purify', '_post']
// 转换之前的处理
if (rule._pre) {
params = rule._pre(params)
}
// 净化参数
let purify = { shouldDelete: new Set([]) }
if (rule._purify) {
const _purify = rule._purify
for (const purifyKey in _purify) {
_purify[purifyKey] = new Set(_purify[purifyKey])
}
purify = Object.assign(purify, _purify)
}
if (isPlainObject(rule)) {
for (const key in rule) {
const parser = rule[key]
if (isFn(parser) && internalKeys.indexOf(key) === -1) {
params[key] = parser(params)
} else if (typeof parser === 'string' && internalKeys.indexOf(key) === -1) {
// 直接转换属性名称的删除旧属性名
params[key] = params[parser]
purify.shouldDelete.add(parser)
}
}
} else if (isFn(rule)) {
params = rule(params)
}
if (purify.shouldDelete) {
for (const item of purify.shouldDelete) {
delete params[item]
}
}
// 转换之后的处理
if (rule._post) {
params = rule._post(params)
}
return params
}
/**
* 返回一个提供应用上下文的应用实例应用实例挂载的整个组件树共享同一个上下文
* @param {class} ApiClass 实例类
* @param {Object} options 参数
* @returns {Object} 实例类对象
*/
module.exports = function createApi (ApiClass, options) {
const apiInstance = new ApiClass(options)
return new Proxy(apiInstance, {
get: function (obj, prop) {
if (typeof obj[prop] === 'function' && prop.indexOf('_') !== 0 && obj._protocols && obj._protocols[prop]) {
const protocol = obj._protocols[prop]
return async function (params) {
params = parseParams(params, protocol.args)
let result = await obj[prop](params)
result = parseParams(result, protocol.returnValue)
return result
}
} else {
return obj[prop]
}
}
})
}

@ -0,0 +1,19 @@
/**
* @class UniCloudError 错误处理模块
*/
module.exports = class UniCloudError extends Error {
constructor (options) {
super(options.message)
this.errMsg = options.message || ''
Object.defineProperties(this, {
message: {
get () {
return `errCode: ${options.code || ''} | errMsg: ` + this.errMsg
},
set (msg) {
this.errMsg = msg
}
}
})
}
}

@ -0,0 +1,6 @@
module.exports = {
UniCloudError: require('./error'),
createApi: require('./create-api'),
... require('./utils')
}

@ -0,0 +1,197 @@
const _toString = Object.prototype.toString
const hasOwnProperty = Object.prototype.hasOwnProperty
/**
* 检查对象是否包含某个属性
* @param {Object} obj 对象
* @param {String} key 属性键值
*/
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}
/**
* 参数是否为JavaScript的简单对象
* @param {Object} obj
* @returns {Boolean} true|false
*/
function isPlainObject(obj) {
return _toString.call(obj) === '[object Object]'
}
/**
* 是否为函数
* @param {String} fn 函数名
*/
function isFn(fn) {
return typeof fn === 'function'
}
/**
* 深度克隆对象
* @param {Object} obj
*/
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj))
}
/**
* 解析客户端上报的参数
* @param {String} primitiveParams 原始参数
* @param {Object} context 附带的上下文
*/
function parseUrlParams(primitiveParams, context) {
if (!primitiveParams) {
return primitiveParams
}
let params = {}
if(typeof primitiveParams === 'string') {
params = primitiveParams.split('&').reduce((res, cur) => {
const arr = cur.split('=')
return Object.assign({
[arr[0]]: arr[1]
}, res)
}, {})
} else {
//转换参数类型--兼容性
for(let key in primitiveParams) {
if(typeof primitiveParams[key] === 'number') {
params[key] = primitiveParams[key] + ''
} else {
params[key] = primitiveParams[key]
}
}
}
//原以下数据要从客户端上报现调整为如果以下参数客户端未上报则通过请求附带的context参数中获取
const convertParams = {
//appid
ak: 'appId',
//当前登录用户编号
uid: 'uid',
//设备编号
did: 'deviceId',
//uni-app 运行平台,与条件编译平台相同。
up: 'uniPlatform',
//操作系统名称
p: 'osName',
//因为p参数可能会被前端覆盖掉所以这里单独拿出来一个osName
on: 'osName',
//客户端ip
ip: 'clientIP',
//客户端的UA
ua: 'userAgent',
//当前服务空间编号
spid: 'spaceId',
//当前服务空间提供商
sppd: 'provider',
//应用版本号
v: 'appVersion',
//rom 名称
rn: 'romName',
//rom 版本
rv: 'romVersion',
//操作系统版本
sv: 'osVersion',
//操作系统语言
lang: 'osLanguage',
//操作系统主题
ot: 'osTheme',
//设备类型
dtp: 'deviceType',
//设备品牌
brand: 'deviceBrand',
//设备型号
md: 'deviceModel',
//设备像素比
pr: 'devicePixelRatio',
//可使用窗口宽度
ww: 'windowWidth',
//可使用窗口高度
wh: 'windowHeight',
//屏幕宽度
sw: 'screenWidth',
//屏幕高度
sh: 'screenHeight',
}
context = context ? context : {}
for (let key in convertParams) {
if (!params[key] && context[convertParams[key]]) {
params[key] = context[convertParams[key]]
}
}
return params
}
/**
* 解析url
* @param {String} url
*/
function parseUrl(url) {
if (typeof url !== "string" || !url) {
return false
}
const urlInfo = url.split('?')
baseurl = urlInfo[0]
if (baseurl !== '/' && baseurl.indexOf('/') === 0) {
baseurl = baseurl.substr(1)
}
return {
path: baseurl,
query: urlInfo[1] ? decodeURI(urlInfo[1]) : ''
}
}
//加载配置中心-uni-config-center
let createConfig
try {
createConfig = require('uni-config-center')
} catch (e) {}
/**
* 获取配置文件信息
* @param {String} file 配置文件名称
* @param {String} key 配置参数键值
*/
function getConfig(file, key) {
if (!file) {
return false
}
const uniConfig = createConfig && createConfig({
pluginId: 'uni-stat'
})
if (!uniConfig || !uniConfig.hasFile(file + '.json')) {
console.error('Not found the config file')
return false
}
const config = uniConfig.requireFile(file)
return key ? config[key] : config
}
/**
* 休眠
* @param {Object} ms 休眠时间毫秒
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(() => resolve(), ms))
}
module.exports = {
hasOwn,
isPlainObject,
isFn,
deepClone,
parseUrlParams,
parseUrl,
getConfig,
sleep
}

@ -0,0 +1,371 @@
/**
* @class DateTime
* @description 日期处理模块
*/
module.exports = class DateTime {
constructor() {
//默认日期展示格式
this.defaultDateFormat = 'Y-m-d H:i:s'
//默认时区
this.defaultTimezone = 8
this.setTimeZone(this.defaultTimezone)
}
/**
* 设置时区
* @param {Number} timezone 时区
*/
setTimeZone(timezone) {
if (timezone) {
this.timezone = parseInt(timezone)
}
return this
}
/**
* 获取 Date对象
* @param {Date|Time} time
*/
getDateObj(time) {
return time ? new Date(time) : new Date()
}
/**
* 获取毫秒/秒级时间戳
* @param {DateTime} datetime 日期 '2022-04-21 00:00:00'
* @param {Boolean} showSenconds 是否显示为秒级时间戳
*/
getTime(datetime, showSenconds) {
let time = this.getDateObj(datetime).getTime()
if (showSenconds) {
time = Math.trunc(time / 1000)
}
return time
}
/**
* 获取日期
* @param {String} dateFormat 日期格式
* @param {Time} time 时间戳
*/
getDate(dateFormat, time) {
return this.dateFormat(dateFormat, time)
}
/**
* 获取日期在不同时区下的时间戳
* @param {Date|Time}} time 日期或时间戳
* @param {Object} timezone 时区
*/
getTimeByTimeZone(time, timezone) {
this.setTimeZone(timezone)
const thisDate = time ? new Date(time) : new Date()
const localTime = thisDate.getTime()
const offset = thisDate.getTimezoneOffset()
const utc = offset * 60000 + localTime
return utc + (3600000 * this.timezone)
}
/**
* 获取时间信息
* @param {Time} time 时间戳
* @param {Boolean} full 是否完整展示, 为true时小于10的位会自动补0
*/
getTimeInfo(time, full = true) {
time = this.getTimeByTimeZone(time)
const date = this.getDateObj(time)
const retData = {
nYear: date.getFullYear(),
nMonth: date.getMonth() + 1,
nWeek: date.getDay() || 7,
nDay: date.getDate(),
nHour: date.getHours(),
nMinutes: date.getMinutes(),
nSeconds: date.getSeconds()
}
if (full) {
for (const k in retData) {
if (retData[k] < 10) {
retData[k] = '0' + retData[k]
}
}
}
return retData
}
/**
* 时间格式转换
* @param {String} format 展示格式如:Y-m-d H:i:s
* @param {Time} time 时间戳
*/
dateFormat(format, time) {
const timeInfo = this.getTimeInfo(time)
format = format || this.defaultDateFormat
let date = format
if (format.indexOf('Y') > -1) {
date = date.replace(/Y/, timeInfo.nYear)
}
if (format.indexOf('m') > -1) {
date = date.replace(/m/, timeInfo.nMonth)
}
if (format.indexOf('d') > -1) {
date = date.replace(/d/, timeInfo.nDay)
}
if (format.indexOf('H') > -1) {
date = date.replace(/H/, timeInfo.nHour)
}
if (format.indexOf('i') > -1) {
date = date.replace(/i/, timeInfo.nMinutes)
}
if (format.indexOf('s') > -1) {
date = date.replace(/s/, timeInfo.nSeconds)
}
return date
}
/**
* 获取utc格式时间
* @param {Date|Time} datetime 日期或时间戳
*/
getUTC(datetime) {
return this.getDateObj(datetime).toUTCString()
}
/**
* 获取ISO 格式时间
* @param {Date|Time} datetime 日期或时间戳
*/
getISO(datetime) {
return this.getDateObj(datetime).toISOString()
}
/**
* 获取两时间相差天数
* @param {Time} time1 时间戳
* @param {Time} time2 时间戳
*/
getDiffDays(time1, time2) {
if (!time1) {
return false
}
time2 = time2 ? time2 : this.getTime()
let diffTime = time2 - time1
if (diffTime < 0) {
diffTime = Math.abs(diffTime)
}
return Math.ceil(diffTime / 86400000)
}
/**
* 字符串转时间戳
* @param {Object} str 字符串类型的时间戳
*/
strToTime(str) {
if (Array.from(str).length === 10) {
str += '000'
}
return this.getTime(parseInt(str))
}
/**
* 根据设置的天数获取指定日期N天后的时间戳
* @param {Number} days 天数
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeBySetDays(days, time, getAll = false) {
const date = this.getDateObj(time)
date.setDate(date.getDate() + days)
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate('Y-m-d 00:00:00', startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据设置的小时数获取指定日期N小时后的时间戳
* @param {Number} hours 小时数
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定时间初始时间戳该小时00:00的时间戳
*/
getTimeBySetHours(hours, time, getAll = false) {
const date = this.getDateObj(time)
date.setHours(date.getHours() + hours)
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate('Y-m-d H:00:00', startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据设置的周数获取指定日期N周后的时间戳
* @param {Number} weeks 周数
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeBySetWeek(weeks, time, getAll = false) {
const date = this.getDateObj(time)
const dateInfo = this.getTimeInfo(time)
const day = dateInfo.nWeek
const offsetDays = 1 - day
weeks = weeks * 7 + offsetDays
date.setDate(date.getDate() + weeks)
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate('Y-m-d 00:00:00', startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据设置的月数获取指定日期N月后的时间戳
* @param {Number} monthes 月数
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeBySetMonth(monthes, time, getAll = false) {
const date = this.getDateObj(time)
date.setMonth(date.getMonth() + monthes)
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate('Y-m-01 00:00:00', startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据设置的季度数获取指定日期N个季度后的时间戳
* @param {Number} quarter 季度
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeBySetQuarter(quarter, time, getAll = false) {
const date = this.getDateObj(time)
const dateInfo = this.getTimeInfo(time)
date.setMonth(date.getMonth() + quarter * 3)
const month = date.getMonth() + 1;
let quarterN;
let mm;
if ([1,2,3].indexOf(month) > -1) {
// 第1季度
mm = "01";
} else if ([4,5,6].indexOf(month) > -1) {
// 第2季度
mm = "04";
} else if ([7,8,9].indexOf(month) > -1) {
// 第3季度
mm = "07";
} else if ([10,11,12].indexOf(month) > -1) {
// 第4季度
mm = "10";
}
let yyyy = date.getFullYear();
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate(`${yyyy}-${mm}-01 00:00:00`, startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据设置的年数获取指定日期N年后的时间戳
* @param {Number} year 月数
* @param {Date|Time} time 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeBySetYear(year, time, getAll = false) {
const date = this.getDateObj(time)
date.setFullYear(date.getFullYear() + year)
let startTime = date.getTime()
if (!getAll) {
const realdate = this.getDate('Y-01-01 00:00:00', startTime)
startTime = this.getTimeByDateAndTimezone(realdate)
}
return startTime
}
/**
* 根据时区获取指定时间的偏移时间
* @param {Date|Time} 指定的日期或时间戳
* @param {Number} timezone 时区
*/
getTimeByDateAndTimezone(date, timezone) {
if (!timezone) {
timezone = this.timezone
}
const thisDate = this.getDateObj(date)
const thisTime = thisDate.getTime()
const offset = thisDate.getTimezoneOffset()
const offsetTime = offset * 60000 + timezone * 3600000
return thisTime - offsetTime
}
/**
* 根据指定的时间类型获取时间范围
* @param {String} type 时间类型 hour:小时 day: week: month
* @param {Number} offset 时间的偏移量
* @param {Date|Time} thistime 指定的日期或时间戳
* @param {Boolean} getAll 是否获取完整时间戳 false 时返回指定日期初始时间戳当天00:00:00的时间戳
*/
getTimeDimensionByType(type, offset = 0, thistime, getAll = false) {
let startTime = 0
let endTime = 0
switch (type) {
case 'hour': {
startTime = this.getTimeBySetHours(offset, thistime, getAll)
endTime = getAll ? startTime : startTime + 3599999
break
}
case 'day': {
startTime = this.getTimeBySetDays(offset, thistime, getAll)
endTime = getAll ? startTime : startTime + 86399999
break
}
case 'week': {
startTime = this.getTimeBySetWeek(offset, thistime, getAll)
endTime = getAll ? startTime + 86400000 * 6 : startTime + 86400000 * 6 + 86399999
break
}
case 'month': {
startTime = this.getTimeBySetMonth(offset, thistime, getAll)
const date = this.getDateObj(this.getDate('Y-m-d H:i:s', startTime))
const nextMonthFirstDayTime = new Date(date.getFullYear(), date.getMonth() + 1, 1).getTime()
endTime = getAll ? nextMonthFirstDayTime - 86400000 : this.getTimeByDateAndTimezone(
nextMonthFirstDayTime) - 1
break
}
case 'quarter': {
startTime = this.getTimeBySetQuarter(offset, thistime, getAll)
const date = this.getDateObj(this.getDate('Y-m-d H:i:s', startTime))
const nextMonthFirstDayTime = new Date(date.getFullYear(), date.getMonth() + 3, 1).getTime()
endTime = getAll ? nextMonthFirstDayTime - 86400000 : this.getTimeByDateAndTimezone(
nextMonthFirstDayTime) - 1
break
}
case 'year': {
startTime = this.getTimeBySetYear(offset, thistime, getAll)
const date = this.getDateObj(this.getDate('Y-m-d H:i:s', startTime))
const nextFirstDayTime = new Date(date.getFullYear() + 1, 0, 1).getTime()
endTime = getAll ? nextFirstDayTime - 86400000 : this.getTimeByDateAndTimezone(
nextFirstDayTime) - 1
break
}
}
return {
startTime,
endTime
}
}
}

@ -0,0 +1,4 @@
module.exports = {
DateTime: require('./date'),
UniCrypto: require('./uni-crypto')
}

@ -0,0 +1,98 @@
/**
* @class UniCrypto 数据加密服务
* @function init 初始化函数
* @function showConfig 返回配置信息函数
* @function getCrypto 返回原始crypto对象函数
* @function aesEncode AES加密函数
* @function aesDecode AES解密函数
* @function md5 MD5加密函数
*/
const crypto = require('crypto')
module.exports = class UniCrypto {
constructor(config) {
this.init(config)
}
/**
* 配置初始化函数
* @param {Object} config
*/
init(config) {
this.config = {
//AES加密默认参数
AES: {
mod: 'aes-128-cbc',
pasword: 'UniStat!010',
iv: 'UniStativ',
charset: 'utf8',
encodeReturnType: 'base64'
},
//MD5加密默认参数
MD5: {
encodeReturnType: 'hex'
},
...config || {}
}
return this
}
/**
* 返回配置信息函数
*/
showConfig() {
return this.config
}
/**
* 返回原始crypto对象函数
*/
getCrypto() {
return crypto
}
/**
* AES加密函数
* @param {String} data 加密数据明文
* @param {String} encodeReturnType 返回加密数据类型base64
* @param {String} key 密钥
* @param {String} iv 偏移量
* @param {String} mod 模式
* @param {String} charset 编码
*/
aesEncode(data, encodeReturnType, key, iv, mod, charset) {
const cipher = crypto.createCipheriv(mod || this.config.AES.mod, key || this.config.AES.pasword, iv ||
this.config.AES.iv)
let crypted = cipher.update(data, charset || this.config.AES.charset, 'binary')
crypted += cipher.final('binary')
crypted = Buffer.from(crypted, 'binary').toString(encodeReturnType || this.config.AES.encodeReturnType)
return crypted
}
/**
* AES解密函数
* @param {Object} crypted 加密数据密文
* @param {Object} encodeReturnType 返回加密数据类型base64
* @param {Object} key 密钥
* @param {Object} iv 偏移量
* @param {Object} mod 模式
* @param {Object} charset 编码
*/
aesDecode(crypted, encodeReturnType, key, iv, mod, charset) {
crypted = Buffer.from(crypted, encodeReturnType || this.config.AES.encodeReturnType).toString('binary')
const decipher = crypto.createDecipheriv(mod || this.config.AES.mod, key || this.config.AES.pasword,
iv || this.config.AES.iv)
let decoded = decipher.update(crypted, 'binary', charset || this.config.AES.charset)
decoded += decipher.final(charset || this.config.AES.charset)
return decoded
}
/**
* @param {Object} str 加密字符串
* @param {Object} encodeReturnType encodeReturnType 返回加密数据类型hex(转为16进制)
*/
md5(str, encodeReturnType) {
const md5Mod = crypto.createHash('md5')
md5Mod.update(str)
return md5Mod.digest(encodeReturnType || this.config.MD5.encodeReturnType)
}
}

@ -0,0 +1,528 @@
/**
* @class ActiveDevices 活跃设备模型 - 每日跑批合并仅添加本周/本月首次访问的设备
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const Version = require('./version')
const SessionLog = require('./sessionLog')
const {
DateTime,
UniCrypto
} = require('../lib')
module.exports = class ActiveDevices extends BaseMod {
constructor() {
super()
this.tableName = 'active-devices'
this.platforms = []
this.channels = []
this.versions = []
}
/**
* @desc 活跃设备统计 - 为周统计/月统计提供周活/月活数据
* @param {date|time} date
* @param {bool} reset
*/
async stat(date, reset) {
const dateTime = new DateTime()
const dateDimension = dateTime.getTimeDimensionByType('day', -1, date)
this.startTime = dateDimension.startTime
// 查看当前时间段数据是否已存在,防止重复生成
if (!reset) {
const checkRes = await this.getCollection(this.tableName).where({
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
}).get()
if (checkRes.data.length > 0) {
console.log('data have exists')
return {
code: 1003,
msg: 'Devices data in this time have already existed'
}
}
} else {
const delRes = await this.delete(this.tableName, {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
})
console.log('Delete old data result:', JSON.stringify(delRes))
}
const sessionLog = new SessionLog()
const statRes = await this.aggregate(sessionLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
is_first_visit: 1,
create_time: 1,
device_id: 1
},
match: {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
device_id: '$device_id'
},
is_new: {
$max: '$is_first_visit'
},
create_time: {
$min: '$create_time'
}
},
sort: {
create_time: 1
},
getAll: true
})
let res = {
code: 0,
msg: 'success'
}
// if (this.debug) {
// console.log('statRes', JSON.stringify(statRes))
// }
if (statRes.data.length > 0) {
const uniCrypto = new UniCrypto()
// 同应用、平台、渠道、版本的数据合并
const statData = [];
let statKey;
let data
const statOldRes = await this.aggregate(sessionLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
is_first_visit: 1,
create_time: 1,
old_device_id: 1
},
match: {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
},
old_device_id: {$exists: true}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
old_device_id: '$old_device_id'
},
create_time: {
$min: '$create_time'
}
},
sort: {
create_time: 1
},
getAll: true
})
if (this.debug) {
console.log('statOldRes', JSON.stringify(statOldRes))
}
for (const sti in statRes.data) {
data = statRes.data[sti]
statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
.channel)
if (!statData[statKey]) {
statData[statKey] = {
appid: data._id.appid,
platform: data._id.platform,
version: data._id.version,
channel: data._id.channel,
device_ids: [],
old_device_ids: [],
info: [],
old_info: []
}
statData[statKey].device_ids.push(data._id.device_id)
statData[statKey].info[data._id.device_id] = {
is_new: data.is_new,
create_time: data.create_time
}
} else {
statData[statKey].device_ids.push(data._id.device_id)
statData[statKey].info[data._id.device_id] = {
is_new: data.is_new,
create_time: data.create_time
}
}
}
if(statOldRes.data.length) {
const oldDeviceIds = []
for(const osti in statOldRes.data) {
if(!statOldRes.data[osti]._id.old_device_id) {
continue
}
oldDeviceIds.push(statOldRes.data[osti]._id.old_device_id)
}
if(oldDeviceIds.length) {
const statOldDidRes = await this.aggregate(sessionLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
is_first_visit: 1,
create_time: 1,
device_id: 1
},
match: {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
},
device_id: {$in: oldDeviceIds}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
old_device_id: '$device_id'
},
create_time: {
$min: '$create_time'
}
},
sort: {
create_time: 1
},
getAll: true
})
if(statOldDidRes.data.length){
for(const osti in statOldDidRes.data) {
data = statOldDidRes.data[osti]
statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
.channel)
if(!data._id.old_device_id) {
continue
}
if (!statData[statKey]) {
statData[statKey] = {
appid: data._id.appid,
platform: data._id.platform,
version: data._id.version,
channel: data._id.channel,
device_ids: [],
old_device_ids: [],
old_info: []
}
statData[statKey].old_device_ids.push(data._id.old_device_id)
} else {
statData[statKey].old_device_ids.push(data._id.old_device_id)
}
if(!statData[statKey].old_info[data._id.old_device_id]) {
statData[statKey].old_info[data._id.old_device_id] = {
create_time: data.create_time
}
}
}
}
}
}
this.fillData = []
for (const sk in statData) {
await this.getFillData(statData[sk])
}
if (this.fillData.length > 0) {
res = await this.batchInsert(this.tableName, this.fillData)
}
}
return res
}
/**
* 获取填充数据
* @param {Object} data
*/
async getFillData(data) {
// 平台信息
let platformInfo = null
if (this.platforms && this.platforms[data.platform]) {
platformInfo = this.platforms[data.platform]
} else {
const platform = new Platform()
platformInfo = await platform.getPlatformAndCreate(data.platform, null)
if (!platformInfo || platformInfo.length === 0) {
platformInfo._id = ''
}
this.platforms[data.platform] = platformInfo
if (this.debug) {
console.log('platformInfo', JSON.stringify(platformInfo))
}
}
// 渠道信息
let channelInfo = null
const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel
if (this.channels && this.channels[channelKey]) {
channelInfo = this.channels[channelKey]
} else {
const channel = new Channel()
channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel)
if (!channelInfo || channelInfo.length === 0) {
channelInfo._id = ''
}
this.channels[channelKey] = channelInfo
if (this.debug) {
console.log('channelInfo', JSON.stringify(channelInfo))
}
}
// 版本信息
let versionInfo = null
const versionKey = data.appid + '_' + data.platform + '_' + data.version
if (this.versions && this.versions[versionKey]) {
versionInfo = this.versions[versionKey]
} else {
const version = new Version()
versionInfo = await version.getVersionAndCreate(data.appid, data.platform, data.version)
if (!versionInfo || versionInfo.length === 0) {
versionInfo._id = ''
}
this.versions[versionKey] = versionInfo
if (this.debug) {
console.log('versionInfo', JSON.stringify(versionInfo))
}
}
const datetime = new DateTime()
const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime)
const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime)
if(data.device_ids) {
// 取出本周已经存储的device_id
const weekHaveDeviceList = []
const haveWeekList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
device_id: {
$in: data.device_ids
},
dimension: 'week',
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
}, {
device_id: 1
})
if (haveWeekList.data.length > 0) {
for (const hui in haveWeekList.data) {
weekHaveDeviceList.push(haveWeekList.data[hui].device_id)
}
}
if (this.debug) {
console.log('weekHaveDeviceList', JSON.stringify(weekHaveDeviceList))
}
// 取出本月已经存储的device_id
const monthHaveDeviceList = []
const haveMonthList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
device_id: {
$in: data.device_ids
},
dimension: 'month',
create_time: {
$gte: dateMonthDimension.startTime,
$lte: dateMonthDimension.endTime
}
}, {
device_id: 1
})
if (haveMonthList.data.length > 0) {
for (const hui in haveMonthList.data) {
monthHaveDeviceList.push(haveMonthList.data[hui].device_id)
}
}
if (this.debug) {
console.log('monthHaveDeviceList', JSON.stringify(monthHaveDeviceList))
}
//数据填充
for (const ui in data.device_ids) {
//周活跃数据填充
if (!weekHaveDeviceList.includes(data.device_ids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
is_new: data.info[data.device_ids[ui]].is_new,
device_id: data.device_ids[ui],
dimension: 'week',
create_time: data.info[data.device_ids[ui]].create_time
})
}
//月活跃数据填充
if (!monthHaveDeviceList.includes(data.device_ids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
is_new: data.info[data.device_ids[ui]].is_new,
device_id: data.device_ids[ui],
dimension: 'month',
create_time: data.info[data.device_ids[ui]].create_time
})
}
}
}
if(data.old_device_ids) {
// 取出本周已经存储的old_device_id
const weekHaveOldDeviceList = []
const haveOldWeekList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
device_id: {
$in: data.old_device_ids
},
dimension: 'week-old',
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
}, {
device_id: 1
})
if (haveOldWeekList.data.length > 0) {
for (const hui in haveOldWeekList.data) {
weekHaveOldDeviceList.push(haveOldWeekList.data[hui].device_id)
}
}
if (this.debug) {
console.log('weekHaveOldDeviceList', JSON.stringify(weekHaveOldDeviceList))
}
// 取出本月已经存储的old_device_id
const monthHaveOldDeviceList = []
const haveOldMonthList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
device_id: {
$in: data.old_device_ids
},
dimension: 'month-old',
create_time: {
$gte: dateMonthDimension.startTime,
$lte: dateMonthDimension.endTime
}
}, {
device_id: 1
})
if (haveOldMonthList.data.length > 0) {
for (const hui in haveOldMonthList.data) {
monthHaveOldDeviceList.push(haveOldMonthList.data[hui].device_id)
}
}
if (this.debug) {
console.log('monthHaveOldDeviceList', JSON.stringify(monthHaveOldDeviceList))
}
//数据填充
for (const ui in data.old_device_ids) {
//周活跃数据填充
if (!weekHaveOldDeviceList.includes(data.old_device_ids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
is_new: 0,
device_id: data.old_device_ids[ui],
dimension: 'week-old',
create_time: data.old_info[data.old_device_ids[ui]].create_time
})
}
//月活跃数据填充
if (!monthHaveOldDeviceList.includes(data.old_device_ids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
is_new: 0,
device_id: data.old_device_ids[ui],
dimension: 'month-old',
create_time: data.old_info[data.old_device_ids[ui]].create_time
})
}
}
}
return true
}
/**
* 日志清理此处日志为临时数据并不需要自定义清理默认为固定值即可
*/
async clean() {
// 清除周数据周留存统计最高需要10周数据多余的为无用数据
const weeks = 10
console.log('Clean device\'s weekly logs - week:', weeks)
const dateTime = new DateTime()
const res = await this.delete(this.tableName, {
dimension: 'week',
create_time: {
$lt: dateTime.getTimeBySetWeek(0 - weeks)
}
})
if (!res.code) {
console.log('Clean device\'s weekly logs - res:', res)
}
// 清除月数据月留存统计最高需要10个月数据多余的为无用数据
const monthes = 10
console.log('Clean device\'s monthly logs - month:', monthes)
const monthRes = await this.delete(this.tableName, {
dimension: 'month',
create_time: {
$lt: dateTime.getTimeBySetMonth(0 - monthes)
}
})
if (!monthRes.code) {
console.log('Clean device\'s monthly logs - res:', res)
}
return monthRes
}
}

@ -0,0 +1,314 @@
/**
* @class ActiveUsers 活跃用户模型 - 每日跑批合并仅添加本周/本月首次访问的用户
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const Version = require('./version')
const UserSessionLog = require('./userSessionLog')
const {
DateTime,
UniCrypto
} = require('../lib')
module.exports = class ActiveUsers extends BaseMod {
constructor() {
super()
this.tableName = 'active-users'
this.platforms = []
this.channels = []
this.versions = []
}
async stat(date, reset) {
const dateTime = new DateTime()
const dateDimension = dateTime.getTimeDimensionByType('day', -1, date)
this.startTime = dateDimension.startTime
// 查看当前时间段数据是否已存在,防止重复生成
if (!reset) {
const checkRes = await this.getCollection(this.tableName).where({
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
}).get()
if (checkRes.data.length > 0) {
console.log('data have exists')
return {
code: 1003,
msg: 'Users data in this time have already existed'
}
}
} else {
const delRes = await this.delete(this.tableName, {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
})
console.log('Delete old data result:', JSON.stringify(delRes))
}
const userSessionLog = new UserSessionLog()
const statRes = await this.aggregate(userSessionLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
create_time: 1,
uid: 1
},
match: {
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
uid: '$uid'
},
create_time: {
$min: '$create_time'
}
},
sort: {
create_time: 1
},
getAll: true
})
let res = {
code: 0,
msg: 'success'
}
// if (this.debug) {
// console.log('statRes', JSON.stringify(statRes))
// }
if (statRes.data.length > 0) {
const uniCrypto = new UniCrypto()
// 同应用、平台、渠道、版本的数据合并
const statData = [];
let statKey;
let data
for (const sti in statRes.data) {
data = statRes.data[sti]
statKey = uniCrypto.md5(data._id.appid + data._id.platform + data._id.version + data._id
.channel)
if (!statData[statKey]) {
statData[statKey] = {
appid: data._id.appid,
platform: data._id.platform,
version: data._id.version,
channel: data._id.channel,
uids: [],
info: []
}
statData[statKey].uids.push(data._id.uid)
statData[statKey].info[data._id.uid] = {
create_time: data.create_time
}
} else {
statData[statKey].uids.push(data._id.uid)
statData[statKey].info[data._id.uid] = {
create_time: data.create_time
}
}
}
this.fillData = []
for (const sk in statData) {
await this.getFillData(statData[sk])
}
if (this.fillData.length > 0) {
res = await this.batchInsert(this.tableName, this.fillData)
}
}
return res
}
async getFillData(data) {
// 平台信息
let platformInfo = null
if (this.platforms && this.platforms[data.platform]) {
platformInfo = this.platforms[data.platform]
} else {
const platform = new Platform()
platformInfo = await platform.getPlatformAndCreate(data.platform, null)
if (!platformInfo || platformInfo.length === 0) {
platformInfo._id = ''
}
this.platforms[data.platform] = platformInfo
if (this.debug) {
console.log('platformInfo', JSON.stringify(platformInfo))
}
}
// 渠道信息
let channelInfo = null
const channelKey = data.appid + '_' + platformInfo._id + '_' + data.channel
if (this.channels && this.channels[channelKey]) {
channelInfo = this.channels[channelKey]
} else {
const channel = new Channel()
channelInfo = await channel.getChannelAndCreate(data.appid, platformInfo._id, data.channel)
if (!channelInfo || channelInfo.length === 0) {
channelInfo._id = ''
}
this.channels[channelKey] = channelInfo
if (this.debug) {
console.log('channelInfo', JSON.stringify(channelInfo))
}
}
// 版本信息
let versionInfo = null
const versionKey = data.appid + '_' + data.platform + '_' + data.version
if (this.versions && this.versions[versionKey]) {
versionInfo = this.versions[versionKey]
} else {
const version = new Version()
versionInfo = await version.getVersionAndCreate(data.appid, data.platform, data.version)
if (!versionInfo || versionInfo.length === 0) {
versionInfo._id = ''
}
this.versions[versionKey] = versionInfo
if (this.debug) {
console.log('versionInfo', JSON.stringify(versionInfo))
}
}
// 是否在本周内已存在
const datetime = new DateTime()
const dateDimension = datetime.getTimeDimensionByType('week', 0, this.startTime)
// 取出本周已经存储的uid
const weekHaveUserList = []
const haveWeekList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
uid: {
$in: data.uids
},
dimension: 'week',
create_time: {
$gte: dateDimension.startTime,
$lte: dateDimension.endTime
}
}, {
uid: 1
})
if (this.debug) {
console.log('haveWeekList', JSON.stringify(haveWeekList))
}
if (haveWeekList.data.length > 0) {
for (const hui in haveWeekList.data) {
weekHaveUserList.push(haveWeekList.data[hui].uid)
}
}
// 取出本月已经存储的uid
const dateMonthDimension = datetime.getTimeDimensionByType('month', 0, this.startTime)
const monthHaveUserList = []
const haveMonthList = await this.selectAll(this.tableName, {
appid: data.appid,
version_id: versionInfo._id,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
uid: {
$in: data.uids
},
dimension: 'month',
create_time: {
$gte: dateMonthDimension.startTime,
$lte: dateMonthDimension.endTime
}
}, {
uid: 1
})
if (this.debug) {
console.log('haveMonthList', JSON.stringify(haveMonthList))
}
if (haveMonthList.data.length > 0) {
for (const hui in haveMonthList.data) {
monthHaveUserList.push(haveMonthList.data[hui].uid)
}
}
for (const ui in data.uids) {
if (!weekHaveUserList.includes(data.uids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
uid: data.uids[ui],
dimension: 'week',
create_time: data.info[data.uids[ui]].create_time
})
}
if (!monthHaveUserList.includes(data.uids[ui])) {
this.fillData.push({
appid: data.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
uid: data.uids[ui],
dimension: 'month',
create_time: data.info[data.uids[ui]].create_time
})
}
}
return true
}
/**
* 日志清理此处日志为临时数据并不需要自定义清理默认为固定值即可
*/
async clean() {
// 清除周数据周留存统计最高需要10周数据多余的为无用数据
const weeks = 10
console.log('Clean user\'s weekly logs - week:', weeks)
const dateTime = new DateTime()
const res = await this.delete(this.tableName, {
dimension: 'week',
create_time: {
$lt: dateTime.getTimeBySetWeek(0 - weeks)
}
})
if (!res.code) {
console.log('Clean user\'s weekly logs - res:', res)
}
// 清除月数据月留存统计最高需要10个月数据多余的为无用数据
const monthes = 10
console.log('Clean user\'s monthly logs - month:', monthes)
const monthRes = await this.delete(this.tableName, {
dimension: 'month',
create_time: {
$lt: dateTime.getTimeBySetMonth(0 - monthes)
}
})
if (!monthRes.code) {
console.log('Clean user\'s monthly logs - res:', res)
}
return monthRes
}
}

@ -0,0 +1,37 @@
/**
* @class AppCrashLogs 原生应用崩溃日志模型
* @function clean 原生应用崩溃日志清理函数
*/
const BaseMod = require('./base')
const {
DateTime,
UniCrypto
} = require('../lib')
module.exports = class AppCrashLogs extends BaseMod {
constructor() {
super()
this.tableName = 'app-crash-logs'
}
/**
* 原生应用崩溃日志清理函数
* @param {Number} days 保留天数
*/
async clean(days = 7) {
days = Math.max(parseInt(days), 1)
console.log('clean app crash logs - day:', days)
const dateTime = new DateTime()
const res = await this.delete(this.tableName, {
create_time: {
$lt: dateTime.getTimeBySetDays(0 - days)
}
})
if (!res.code) {
console.log('clean app crash log:', res)
}
return res
}
}

@ -0,0 +1,485 @@
/**
* @class BaseMod 数据模型基类提供基础服务支持
*/
const {
getConfig
} = require('../../shared')
//基类
module.exports = class BaseMod {
constructor() {
//配置信息
this.config = getConfig('config')
//开启/关闭debug
this.debug = this.config.debug
//主键
this.primaryKey = '_id'
//单次查询最多返回 500 条数据(阿里云500腾讯云1000这里取最小值)
this.selectMaxLimit = 500
//数据表前缀
this.tablePrefix = 'uni-stat'
//数据表连接符
this.tableConnectors = '-'
//数据表名
this.tableName = ''
//参数
this.params = {}
//数据库连接
this._dbConnection()
//redis连接
this._redisConnection()
}
/**
* 建立uniCloud数据库连接
*/
_dbConnection() {
if (!this.db) {
try {
this.db = uniCloud.database()
this.dbCmd = this.db.command
this.dbAggregate = this.dbCmd.aggregate
} catch (e) {
console.error('database connection failed: ' + e)
throw new Error('database connection failed: ' + e)
}
}
}
/**
* 建立uniCloud redis连接
*/
_redisConnection() {
if (this.config.redis && !this.redis) {
try {
this.redis = uniCloud.redis()
} catch (e) {
console.log('redis server connection failed: ' + e)
}
}
}
/**
* 获取uni统计配置项
* @param {String} key
*/
getConfig(key) {
return this.config[key]
}
/**
* 获取带前缀的数据表名称
* @param {String} tab 表名
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
getTableName(tab, useDBPre = true) {
tab = tab || this.tableName
const table = (useDBPre && this.tablePrefix && tab.indexOf(this.tablePrefix) !== 0) ? this.tablePrefix + this
.tableConnectors + tab : tab
return table
}
/**
* 获取数据集
* @param {String} tab表名
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
getCollection(tab, useDBPre = true) {
return this.db.collection(this.getTableName(tab, useDBPre))
}
/**
* 获取reids缓存
* @param {String} key reids缓存键值
*/
async getCache(key) {
if (!this.redis || !key) {
return false
}
let cacheResult = await this.redis.get(key)
if (this.debug) {
console.log('get cache result by key:' + key, cacheResult)
}
if (cacheResult) {
try {
cacheResult = JSON.parse(cacheResult)
} catch (e) {
if (this.debug) {
console.log('json parse error: ' + e)
}
}
}
return cacheResult
}
/**
* 设置redis缓存
* @param {String} key 键值
* @param {String} val
* @param {Number} expireTime 过期时间
*/
async setCache(key, val, expireTime) {
if (!this.redis || !key) {
return false
}
if (val instanceof Object) {
val = JSON.stringify(val)
}
if (this.debug) {
console.log('set cache result by key:' + key, val)
}
return await this.redis.set(key, val, 'EX', expireTime || this.config.cachetime)
}
/**
* 清除redis缓存
* @param {String} key 键值
*/
async clearCache(key) {
if (!this.redis || !key) {
return false
}
if (this.debug) {
console.log('delete cache by key:' + key)
}
return await this.redis.del(key)
}
/**
* 通过数据表主键_id获取数据
* @param {String} tab 表名
* @param {String} id 主键值
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async getById(tab, id, useDBPre = true) {
const condition = {}
condition[this.primaryKey] = id
const info = await this.getCollection(tab, useDBPre).where(condition).get()
return (info && info.data.length > 0) ? info.data[0] : []
}
/**
* 插入数据到数据表
* @param {String} tab 表名
* @param {Object} params 字段参数
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async insert(tab, params, useDBPre = true) {
params = params || this.params
return await this.getCollection(tab, useDBPre).add(params)
}
/**
* 修改数据表数据
* @param {String} tab 表名
* @param {Object} params 字段参数
* @param {Object} condition 条件
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async update(tab, params, condition, useDBPre = true) {
params = params || this.params
return await this.getCollection(tab).where(condition).update(params)
}
/**
* 删除数据表数据
* @param {String} tab 表名
* @param {Object} condition 条件
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async delete(tab, condition, useDBPre = true) {
if (!condition) {
return false
}
return await this.getCollection(tab, useDBPre).where(condition).remove()
}
/**
* 批量插入 - 云服务空间对单条mongo语句执行时间有限制所以批量插入需限制每次执行条数
* @param {String} tab 表名
* @param {Object} data 数据集合
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async batchInsert(tab, data, useDBPre = true) {
let batchInsertNum = this.getConfig('batchInsertNum') || 3000
batchInsertNum = Math.min(batchInsertNum, 5000)
const insertNum = Math.ceil(data.length / batchInsertNum)
let start;
let end;
let fillData;
let insertRes;
const res = {
code: 0,
msg: 'success',
data: {
inserted: 0
}
}
for (let p = 0; p < insertNum; p++) {
start = p * batchInsertNum
end = Math.min(start + batchInsertNum, data.length)
fillData = []
for (let i = start; i < end; i++) {
fillData.push(data[i])
}
if (fillData.length > 0) {
insertRes = await this.insert(tab, fillData, useDBPre)
if (insertRes && insertRes.inserted) {
res.data.inserted += insertRes.inserted
}
}
}
return res
}
/**
* 批量删除 - 云服务空间对单条mongo语句执行时间有限制所以批量删除需限制每次执行条数
* @param {String} tab 表名
* @param {Object} condition 条件
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async batchDelete(tab, condition, useDBPre = true) {
const batchDeletetNum = 5000;
let deleteIds;
let delRes;
let thisCondition
const res = {
code: 0,
msg: 'success',
data: {
deleted: 0
}
}
let run = true
while (run) {
const dataRes = await this.getCollection(tab).where(condition).limit(batchDeletetNum).get()
if (dataRes && dataRes.data.length > 0) {
deleteIds = []
for (let i = 0; i < dataRes.data.length; i++) {
deleteIds.push(dataRes.data[i][this.primaryKey])
}
if (deleteIds.length > 0) {
thisCondition = {}
thisCondition[this.primaryKey] = {
$in: deleteIds
}
delRes = await this.delete(tab, thisCondition, useDBPre)
if (delRes && delRes.deleted) {
res.data.deleted += delRes.deleted
}
}
} else {
run = false
}
}
return res
}
/**
* 基础查询
* @param {String} tab 表名
* @param {Object} params 查询参数 wherewhere条件field返回字段skip跳过的文档数limit返回的记录数orderBy排序count返回查询结果的数量
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async select(tab, params, useDBPre = true) {
const {
where,
field,
skip,
limit,
orderBy,
count
} = params
const query = this.getCollection(tab, useDBPre)
//拼接where条件
if (where) {
if (where.length > 0) {
where.forEach(key => {
query.where(where[key])
})
} else {
query.where(where)
}
}
//排序
if (orderBy) {
Object.keys(orderBy).forEach(key => {
query.orderBy(key, orderBy[key])
})
}
//指定跳过的文档数
if (skip) {
query.skip(skip)
}
//指定返回的记录数
if (limit) {
query.limit(limit)
}
//指定返回字段
if (field) {
query.field(field)
}
//指定返回查询结果数量
if (count) {
return await query.count()
}
//返回查询结果数据
return await query.get()
}
/**
* 查询并返回全部数据
* @param {String} tab 表名
* @param {Object} condition 条件
* @param {Object} field 指定查询返回字段
* @param {Boolean} useDBPre 是否使用数据表前缀
*/
async selectAll(tab, condition, field = {}, useDBPre = true) {
const countRes = await this.getCollection(tab, useDBPre).where(condition).count()
if (countRes && countRes.total > 0) {
const pageCount = Math.ceil(countRes.total / this.selectMaxLimit)
let res, returnData
for (let p = 0; p < pageCount; p++) {
res = await this.getCollection(tab, useDBPre).where(condition).orderBy(this.primaryKey, 'asc').skip(p *
this.selectMaxLimit).limit(this.selectMaxLimit).field(field).get()
if (!returnData) {
returnData = res
} else {
returnData.affectedDocs += res.affectedDocs
for (const i in res.data) {
returnData.data.push(res.data[i])
}
}
}
return returnData
}
return {
affectedDocs: 0,
data: []
}
}
/**
* 聚合查询
* @param {String} tab 表名
* @param {Object} params 聚合参数
*/
async aggregate(tab, params) {
let {
project,
match,
lookup,
group,
skip,
limit,
sort,
getAll,
useDBPre,
addFields
} = params
//useDBPre 是否使用数据表前缀
useDBPre = (useDBPre !== null && useDBPre !== undefined) ? useDBPre : true
const query = this.getCollection(tab, useDBPre).aggregate()
//设置返回字段
if (project) {
query.project(project)
}
//设置匹配条件
if (match) {
query.match(match)
}
//数据表关联
if (lookup) {
query.lookup(lookup)
}
//分组
if (group) {
if (group.length > 0) {
for (const gi in group) {
query.group(group[gi])
}
} else {
query.group(group)
}
}
//添加字段
if (addFields) {
query.addFields(addFields)
}
//排序
if (sort) {
query.sort(sort)
}
//分页
if (skip) {
query.skip(skip)
}
if (limit) {
query.limit(limit)
} else if (!getAll) {
query.limit(this.selectMaxLimit)
}
//如果未指定全部返回则直接返回查询结果
if (!getAll) {
return await query.end()
}
//若指定了全部返回则分页查询全部结果后再返回
const resCount = await query.group({
_id: {},
aggregate_count: {
$sum: 1
}
}).end()
if (resCount && resCount.data.length > 0 && resCount.data[0].aggregate_count > 0) {
//分页查询
const total = resCount.data[0].aggregate_count
const pageCount = Math.ceil(total / this.selectMaxLimit)
let res, returnData
params.limit = this.selectMaxLimit
params.getAll = false
//结果合并
for (let p = 0; p < pageCount; p++) {
params.skip = p * params.limit
res = await this.aggregate(tab, params)
if (!returnData) {
returnData = res
} else {
returnData.affectedDocs += res.affectedDocs
for (const i in res.data) {
returnData.data.push(res.data[i])
}
}
}
return returnData
} else {
return {
affectedDocs: 0,
data: []
}
}
}
}

@ -0,0 +1,107 @@
/**
* @class Channel 渠道模型
*/
const BaseMod = require('./base')
const Scenes = require('./scenes')
const {
DateTime
} = require('../lib')
module.exports = class Channel extends BaseMod {
constructor() {
super()
this.tableName = 'app-channels'
this.scenes = new Scenes()
}
/**
* 获取渠道信息
* @param {String} appid
* @param {String} platformId 平台编号
* @param {String} channel 渠道代码
*/
async getChannel(appid, platformId, channel) {
const cacheKey = 'uni-stat-channel-' + appid + '-' + platformId + '-' + channel
let channelData = await this.getCache(cacheKey)
if (!channelData) {
const channelInfo = await this.getCollection(this.tableName).where({
appid: appid,
platform_id: platformId,
channel_code: channel
}).limit(1).get()
channelData = []
if (channelInfo.data.length > 0) {
channelData = channelInfo.data[0]
if (channelData.channel_name === '') {
const scenesName = await this.scenes.getScenesNameByPlatformId(platformId, channel)
if (scenesName) {
await this.update(this.tableName, {
channel_name: scenesName,
update_time: new DateTime().getTime()
}, {
_id: channelData._id
})
}
}
await this.setCache(cacheKey, channelData)
}
}
return channelData
}
/**
* 获取渠道信息没有则进行创建
* @param {String} appid
* @param {String} platformId
* @param {String} channel
*/
async getChannelAndCreate(appid, platformId, channel) {
if (!appid || !platformId) {
return []
}
const channelInfo = await this.getChannel(appid, platformId, channel)
if (channelInfo.length === 0) {
const thisTime = new DateTime().getTime()
const insertParam = {
appid: appid,
platform_id: platformId,
channel_code: channel,
channel_name: await this.scenes.getScenesNameByPlatformId(platformId, channel),
create_time: thisTime,
update_time: thisTime
}
const res = await this.insert(this.tableName, insertParam)
if (res && res.id) {
return Object.assign(insertParam, {
_id: res.id
})
}
}
return channelInfo
}
/**
* 获取渠道_id
* @param {String} appid
* @param {String} platformId
* @param {String} channel
*/
async getChannelId(appid, platformId, channel) {
const channelInfo = await this.getChannel(appid, platformId, channel)
return channelInfo.length > 0 ? channelInfo._id : ''
}
/**
* 获取渠道码或者场景值
* @param {Object} params 上报参数
*/
getChannelCode(params) {
//小程序未上报渠道则使用场景值
if (params.ch) {
return params.ch
} else if (params.sc && params.ut.indexOf('mp-') === 0) {
return params.sc
}
return this.scenes.defualtCode
}
}

@ -0,0 +1,184 @@
/**
* @class Device 设备模型
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const {
DateTime
} = require('../lib')
module.exports = class Device extends BaseMod {
constructor() {
super()
this.tableName = 'opendb-device'
this.tablePrefix = false
this.cacheKeyPre = 'uni-stat-device-'
}
/**
* 通过设备编号获取设备信息
* @param {Object} deviceId 设备编号
*/
async getDeviceById(deviceId) {
const cacheKey = this.cacheKeyPre + deviceId
let deviceData = await this.getCache(cacheKey)
if (!deviceData) {
const deviceRes = await this.getCollection().where({
device_id: deviceId
}).get()
deviceData = []
if (deviceRes.data.length > 0) {
deviceData = deviceRes.data[0]
await this.setCache(cacheKey, deviceData)
}
}
return deviceData
}
/**
* 设置设备信息
* @param {Object} params 上报参数
*/
async setDevice(params) {
// 设备信息
if (!params.did) {
return {
code: 200,
msg: 'Parameter "did" not found'
}
}
const deviceData = await this.getDeviceById(params.did)
//不存在则添加
if(deviceData.length === 0) {
return await this.addDevice(params)
} else {
return await this.updateDevice(params, deviceData)
}
}
/**
* 添加设备信息
* @param {Object} params 上报参数
*/
async addDevice(params) {
const dateTime = new DateTime()
const platform = new Platform()
const fillParams = {
device_id: params.did,
appid: params.ak,
vendor: params.brand ? params.brand : '',
push_clientid: params.cid ? params.cid : '',
imei: params.imei ? params.imei : '',
oaid: params.oaid ? params.oaid : '',
idfa: params.idfa ? params.idfa : '',
imsi: params.imsi ? params.imsi : '',
model: params.md ? params.md : '',
uni_platform: params.up ? params.up : '',
os_name: params.on ? params.on : platform.getOsName(params.p),
os_version: params.sv ? params.sv : '',
os_language: params.lang ? params.lang : '',
os_theme: params.ot ? params.ot : '',
pixel_ratio: params.pr ? params.pr : '',
network_model: params.net ? params.net : '',
window_width: params.ww ? params.ww : '',
window_height: params.wh ? params.wh : '',
screen_width: params.sw ? params.sw : '',
screen_height: params.sh ? params.sh : '',
rom_name: params.rn ? params.rn : '',
rom_version: params.rv ? params.rv : '',
location_ip: params.ip ? params.ip : '',
location_latitude: params.lat ? parseFloat(params.lat) : 0,
location_longitude: params.lng ? parseFloat(params.lng) : 0,
location_country: params.cn ? params.cn : '',
location_province: params.pn ? params.pn : '',
location_city: params.ct ? params.ct : '',
create_date: dateTime.getTime(),
last_update_date: dateTime.getTime()
}
const res = await this.insert(this.tableName, fillParams)
if (res && res.id) {
return {
code: 0,
msg: 'success',
}
} else {
return {
code: 500,
msg: 'Device data filled error'
}
}
}
/**
* 修改设备信息
* @param {Object} params
* @param {Object} deviceData
*/
async updateDevice(params, deviceData) {
//最新的参数
const dateTime = new DateTime()
const platform = new Platform()
console.log('device params', params)
const newDeviceParams = {
appid: params.ak,
push_clientid: params.cid ? params.cid : '',
imei: params.imei ? params.imei : '',
oaid: params.oaid ? params.oaid : '',
idfa: params.idfa ? params.idfa : '',
imsi: params.imsi ? params.imsi : '',
uni_platform: params.up ? params.up : '',
os_name: params.on ? params.on : platform.getOsName(params.p),
os_version: params.sv ? params.sv : '',
os_language: params.lang ? params.lang : '',
pixel_ratio: params.pr ? params.pr : '',
network_model: params.net ? params.net : '',
window_width: params.ww ? params.ww : '',
window_height: params.wh ? params.wh : '',
screen_width: params.sw ? params.sw : '',
screen_height: params.sh ? params.sh : '',
rom_name: params.rn ? params.rn : '',
rom_version: params.rv ? params.rv : '',
location_ip: params.ip ? params.ip : '',
location_latitude: params.lat ? parseFloat(params.lat) : '',
location_longitude: params.lng ? parseFloat(params.lng) : '',
location_country: params.cn ? params.cn : '',
location_province: params.pn ? params.pn : '',
location_city: params.ct ? params.ct : '',
}
//检查是否有需要更新的数据
const updateData = {}
for(let key in newDeviceParams) {
if(newDeviceParams[key] && newDeviceParams[key] !== deviceData[key]) {
updateData[key] = newDeviceParams[key]
}
}
if(Object.keys(updateData).length) {
if(this.debug) {
console.log('Device need to update', updateData)
}
//数据更新
updateData.last_update_date = dateTime.getTime()
await this.update(this.tableName, updateData, {device_id: params.did})
} else {
if(this.debug) {
console.log('Device not need update', newDeviceParams)
}
}
return {
code: 0,
msg: 'success'
}
}
async bindPush(params) {
if (!params.cid) {
return {
code: 200,
msg: 'Parameter "cid" not found'
}
}
return await this.setDevice(params)
}
}

@ -0,0 +1,141 @@
/**
* @class ErrorLog 错误日志模型
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const {
DateTime,
UniCrypto
} = require('../lib')
module.exports = class ErrorLog extends BaseMod {
constructor() {
super()
this.tableName = 'error-logs'
}
/**
* 错误日志数据填充
* @param {Object} reportParams 上报参数
*/
async fill(reportParams) {
let params, errorHash, errorCount, cacheKey;
const fillParams = []
const platform = new Platform()
const dateTime = new DateTime()
const uniCrypto = new UniCrypto()
const channel = new Channel()
const {
needCheck,
checkTime
} = this.getConfig('errorCheck')
const errorCheckTime = Math.max(checkTime, 1)
let spaceId
let spaceProvider
for (const rk in reportParams) {
params = reportParams[rk]
errorHash = uniCrypto.md5(params.em)
cacheKey = 'error-count-' + errorHash
// 校验在指定时间段内是否已存在相同的错误项
if (needCheck) {
errorCount = await this.getCache(cacheKey)
if (!errorCount) {
errorCount = await this.getCollection(this.tableName).where({
error_hash: errorHash,
create_time: {
$gte: dateTime.getTime() - errorCheckTime * 60000
}
}).count()
if (errorCount && errorCount.total > 0) {
await this.setCache(cacheKey, errorCount, errorCheckTime * 60)
}
}
if (errorCount && errorCount.total > 0) {
if (this.debug) {
console.log('This error have already existsed: ' + params.em)
}
continue
}
}
//获取云端信息
spaceId = null
spaceProvider = null
if (params.spi) {
//云函数调用参数
spaceId = params.spi.spaceId
spaceProvider = params.spi.provider
} else {
//云对象调用参数
if (params.spid) {
spaceId = params.spid
}
if (params.sppd) {
spaceProvider = params.sppd
}
}
// 填充数据
fillParams.push({
appid: params.ak,
version: params.v ? params.v : '',
platform: platform.getPlatformCode(params.ut, params.p),
channel: channel.getChannelCode(params),
device_id: params.did,
uid: params.uid ? params.uid : '',
os: params.on ? params.on : platform.getOsName(params.p),
ua: params.ua ? params.ua : '',
page_url: params.url ? params.url : '',
space_id: spaceId ? spaceId : '',
space_provider: spaceProvider ? spaceProvider : '',
platform_version: params.mpv ? params.mpv : '',
error_msg: params.em ? params.em : '',
error_hash: errorHash,
create_time: dateTime.getTime()
})
}
if (fillParams.length === 0) {
return {
code: 200,
msg: 'Invild param'
}
}
const res = await this.insert(this.tableName, fillParams)
if (res && res.inserted) {
return {
code: 0,
msg: 'success'
}
} else {
return {
code: 500,
msg: 'Filled error'
}
}
}
/**
* 错误日志清理
* @param {Number} days 日志保留天数
*/
async clean(days) {
days = Math.max(parseInt(days), 1)
console.log('clean error logs - day:', days)
const dateTime = new DateTime()
const res = await this.delete(this.tableName, {
create_time: {
$lt: dateTime.getTimeBySetDays(0 - days)
}
})
if (!res.code) {
console.log('clean error log:', res)
}
return res
}
}

@ -0,0 +1,459 @@
/**
* @class ErrorResult 错误结果统计模型
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const Version = require('./version')
const ErrorLog = require('./errorLog')
const AppCrashLogs = require('./appCrashLogs')
const SessionLog = require('./sessionLog')
const {
DateTime
} = require('../lib')
module.exports = class ErrorResult extends BaseMod {
constructor() {
super()
this.tableName = 'error-result'
this.platforms = []
this.channels = []
this.versions = []
this.errors = []
}
/**
* 错误结果统计
* @param {String} type 统计类型 hour实时统计 day按天统计week按周统计 month按月统计
* @param {Date|Time} date 指定日期或时间戳
* @param {Boolean} reset 是否重置为ture时会重置该批次数据
*/
async stat(type, date, reset) {
//前端js错误统计
const resJs = await this.statJs(type, date, reset)
//原生应用崩溃错误统计
const resCrash = await this.statCrash(type, date, reset)
return {
code: 0,
msg: 'success',
data: {
resJs,
resCrash
}
}
}
/**
* 前端js错误结果统计
* @param {String} type 统计类型 hour实时统计 day按天统计week按周统计 month按月统计
* @param {Date|Time} date 指定日期或时间戳
* @param {Boolean} reset 是否重置为ture时会重置该批次数据
*/
async statJs(type, date, reset) {
const allowedType = ['day']
if (!allowedType.includes(type)) {
return {
code: 1002,
msg: 'This type is not allowed'
}
}
this.fillType = type
const dateTime = new DateTime()
const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
this.startTime = dateDimension.startTime
this.endTime = dateDimension.endTime
if (this.debug) {
console.log('dimension time', this.startTime + '--' + this.endTime)
}
// 查看当前时间段日志是否已存在,防止重复生成
if (!reset) {
const checkRes = await this.getCollection(this.tableName).where({
type: 'js',
start_time: this.startTime,
end_time: this.endTime
}).get()
if (checkRes.data.length > 0) {
console.log('error log have existed')
return {
code: 1003,
msg: 'This log have existed'
}
}
} else {
const delRes = await this.delete(this.tableName, {
type: 'js',
start_time: this.startTime,
end_time: this.endTime
})
console.log('delete old data result:', JSON.stringify(delRes))
}
// 数据获取
this.errorLog = new ErrorLog()
const statRes = await this.aggregate(this.errorLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
error_hash: 1,
create_time: 1
},
match: {
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
error_hash: '$error_hash'
},
error_count: {
$sum: 1
}
},
sort: {
error_count: 1
},
getAll: true
})
let res = {
code: 0,
msg: 'success'
}
if (this.debug) {
console.log('statRes', JSON.stringify(statRes))
}
if (statRes.data.length > 0) {
this.fillData = []
for (const i in statRes.data) {
await this.fillJs(statRes.data[i])
}
if (this.fillData.length > 0) {
res = await this.batchInsert(this.tableName, this.fillData)
}
}
return res
}
/**
* 前端js错误统计结果数据填充
* @param {Object} data 数据集合
*/
async fillJs(data) {
// 平台信息
let platformInfo = null
if (this.platforms && this.platforms[data._id.platform]) {
//暂存下数据,减少读库
platformInfo = this.platforms[data._id.platform]
} else {
const platform = new Platform()
platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
if (!platformInfo || platformInfo.length === 0) {
platformInfo._id = ''
}
this.platforms[data._id.platform] = platformInfo
if (this.debug) {
console.log('platformInfo', JSON.stringify(platformInfo))
}
}
// 渠道信息
let channelInfo = null
const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
if (this.channels && this.channels[channelKey]) {
channelInfo = this.channels[channelKey]
} else {
const channel = new Channel()
channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
if (!channelInfo || channelInfo.length === 0) {
channelInfo._id = ''
}
this.channels[channelKey] = channelInfo
if (this.debug) {
console.log('channelInfo', JSON.stringify(channelInfo))
}
}
// 版本信息
let versionInfo = null
const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
if (this.versions && this.versions[versionKey]) {
versionInfo = this.versions[versionKey]
} else {
const version = new Version()
versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
if (!versionInfo || versionInfo.length === 0) {
versionInfo._id = ''
}
this.versions[versionKey] = versionInfo
if (this.debug) {
console.log('versionInfo', JSON.stringify(versionInfo))
}
}
// 错误信息
let errorInfo = null
if (this.errors && this.errors[data._id.error_hash]) {
errorInfo = this.errors[data._id.error_hash]
} else {
const cacheKey = 'uni-stat-errors-' + data._id.error_hash
errorInfo = await this.getCache(cacheKey)
if (!errorInfo) {
errorInfo = await this.getCollection(this.errorLog.tableName).where({
error_hash: data._id.error_hash
}).limit(1).get()
if (!errorInfo || errorInfo.data.length === 0) {
errorInfo.error_msg = ''
} else {
errorInfo = errorInfo.data[0]
await this.setCache(cacheKey, errorInfo)
}
}
this.errors[data._id.error_hash] = errorInfo
}
// 最近一次报错时间
const matchCondition = data._id
Object.assign(matchCondition, {
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
})
const lastErrorLog = await this.getCollection(this.errorLog.tableName).where(matchCondition).orderBy(
'create_time', 'desc').limit(1).get()
let lastErrorTime = ''
if (lastErrorLog && lastErrorLog.data.length > 0) {
lastErrorTime = lastErrorLog.data[0].create_time
}
//数据填充
const datetime = new DateTime()
const insertParams = {
appid: data._id.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
type: 'js',
hash: data._id.error_hash,
msg: errorInfo.error_msg,
count: data.error_count,
last_time: lastErrorTime,
dimension: this.fillType,
stat_date: datetime.getDate('Ymd', this.startTime),
start_time: this.startTime,
end_time: this.endTime
}
this.fillData.push(insertParams)
return insertParams
}
/**
* 原生应用错误结果统计
* @param {String} type 统计类型 hour实时统计 day按天统计week按周统计 month按月统计
* @param {Date|Time} date 指定日期或时间戳
* @param {Boolean} reset 是否重置为ture时会重置该批次数据
*/
async statCrash(type, date, reset) {
const allowedType = ['day']
if (!allowedType.includes(type)) {
return {
code: 1002,
msg: 'This type is not allowed'
}
}
this.fillType = type
const dateTime = new DateTime()
const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
this.startTime = dateDimension.startTime
this.endTime = dateDimension.endTime
if (this.debug) {
console.log('dimension time', this.startTime + '--' + this.endTime)
}
// 查看当前时间段日志是否已存在,防止重复生成
if (!reset) {
const checkRes = await this.getCollection(this.tableName).where({
type: 'crash',
start_time: this.startTime,
end_time: this.endTime
}).get()
if (checkRes.data.length > 0) {
console.log('error log have existed')
return {
code: 1003,
msg: 'This log have existed'
}
}
} else {
const delRes = await this.delete(this.tableName, {
type: 'crash',
start_time: this.startTime,
end_time: this.endTime
})
console.log('delete old data result:', JSON.stringify(delRes))
}
// 数据获取
this.crashLogs = new AppCrashLogs()
const statRes = await this.aggregate(this.crashLogs.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
create_time: 1
},
match: {
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel'
},
error_count: {
$sum: 1
}
},
sort: {
error_count: 1
},
getAll: true
})
let res = {
code: 0,
msg: 'success'
}
if (this.debug) {
console.log('statRes', JSON.stringify(statRes))
}
if (statRes.data.length > 0) {
this.fillData = []
for (const i in statRes.data) {
await this.fillCrash(statRes.data[i])
}
if (this.fillData.length > 0) {
res = await this.batchInsert(this.tableName, this.fillData)
}
}
return res
}
async fillCrash(data) {
// 平台信息
let platformInfo = null
if (this.platforms && this.platforms[data._id.platform]) {
//暂存下数据,减少读库
platformInfo = this.platforms[data._id.platform]
} else {
const platform = new Platform()
platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
if (!platformInfo || platformInfo.length === 0) {
platformInfo._id = ''
}
this.platforms[data._id.platform] = platformInfo
if (this.debug) {
console.log('platformInfo', JSON.stringify(platformInfo))
}
}
// 渠道信息
let channelInfo = null
data._id.channel = data._id.channel ? data._id.channel : '1001'
const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
if (this.channels && this.channels[channelKey]) {
channelInfo = this.channels[channelKey]
} else {
const channel = new Channel()
channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
if (!channelInfo || channelInfo.length === 0) {
channelInfo._id = ''
}
this.channels[channelKey] = channelInfo
if (this.debug) {
console.log('channelInfo', JSON.stringify(channelInfo))
}
}
// 版本信息
let versionInfo = null
const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
if (this.versions && this.versions[versionKey]) {
versionInfo = this.versions[versionKey]
} else {
const version = new Version()
versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
if (!versionInfo || versionInfo.length === 0) {
versionInfo._id = ''
}
this.versions[versionKey] = versionInfo
if (this.debug) {
console.log('versionInfo', JSON.stringify(versionInfo))
}
}
//app启动次数
const sessionLog = new SessionLog()
const sessionTimesRes = await this.getCollection(sessionLog.tableName).where({
appid: data._id.appid,
version: data._id.version,
platform: data._id.platform,
channel: data._id.channel,
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
}).count()
let sessionTimes = 0
if(sessionTimesRes && sessionTimesRes.total > 0) {
sessionTimes = sessionTimesRes.total
} else {
console.log('Not found session logs')
return false
}
//数据填充
const datetime = new DateTime()
const insertParams = {
appid: data._id.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
type: 'crash',
count: data.error_count,
app_launch_count: sessionTimes,
dimension: this.fillType,
stat_date: datetime.getDate('Ymd', this.startTime),
start_time: this.startTime,
end_time: this.endTime
}
this.fillData.push(insertParams)
return insertParams
}
}

@ -0,0 +1,72 @@
/**
* @class StatEvent 事件统计模型
*/
const BaseMod = require('./base')
const {
DateTime
} = require('../lib')
module.exports = class StatEvent extends BaseMod {
constructor() {
super()
this.tableName = 'events'
this.defaultEvent = this.getConfig('event') || {
login: '登录',
register: '注册',
click: '点击',
share: '分享',
pay_success: '支付成功',
pay_fail: '支付失败'
}
}
/**
* 获取事件信息
* @param {String} appid: DCloud appid
* @param {String} eventKey 事件键值
*/
async getEvent(appid, eventKey) {
const cacheKey = 'uni-stat-event-' + appid + '-' + eventKey
let eventData = await this.getCache(cacheKey)
if (!eventData) {
const eventInfo = await this.getCollection(this.tableName).where({
appid: appid,
event_key: eventKey
}).get()
eventData = []
if (eventInfo.data.length > 0) {
eventData = eventInfo.data[0]
await this.setCache(cacheKey, eventData)
}
}
return eventData
}
/**
* 获取事件信息不存在则创建
* @param {String} appid: DCloud appid
* @param {String} eventKey 事件键值
*/
async getEventAndCreate(appid, eventKey) {
const eventInfo = await this.getEvent(appid, eventKey)
if (eventInfo.length === 0) {
const thisTime = new DateTime().getTime()
const insertParam = {
appid: appid,
event_key: eventKey,
event_name: this.defaultEvent[eventKey] ? this.defaultEvent[eventKey] : '',
create_time: thisTime,
update_time: thisTime
}
const res = await this.insert(this.tableName, insertParam)
if (res && res.id) {
return Object.assign(insertParam, {
_id: res.id
})
}
}
return eventInfo
}
}

@ -0,0 +1,156 @@
/**
* @class EventLog 事件日志模型
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const StatEvent = require('./event')
const SessionLog = require('./sessionLog')
const ShareLog = require('./shareLog')
const {
DateTime
} = require('../lib')
module.exports = class EventLog extends BaseMod {
constructor() {
super()
this.tableName = 'event-logs'
this.sessionLogInfo = []
}
/**
* 事件日志填充
* @param {Object} reportParams 上报参数
*/
async fill(reportParams) {
let params;
let sessionKey, sessionLogKey;
let sessionLogInfo;
const sessionData = []
const fillParams = []
const shareParams = []
const sessionLog = new SessionLog()
const event = new StatEvent()
const platform = new Platform()
const dateTime = new DateTime()
const channel = new Channel()
for (const rk in reportParams) {
params = reportParams[rk]
//暂存下会话数据,减少读库
sessionKey = params.ak + params.did + params.p
if (!this.sessionLogInfo[sessionKey]) {
// 会话日志
sessionLogInfo = await sessionLog.getSession(params)
if (sessionLogInfo.code) {
return sessionLogInfo
}
if (this.debug) {
console.log('sessionLogInfo', JSON.stringify(sessionLogInfo))
}
this.sessionLogInfo[sessionKey] = sessionLogInfo
} else {
sessionLogInfo = this.sessionLogInfo[sessionKey]
}
// 会话数据
sessionLogKey = sessionLogInfo.data.sessionLogId.toString()
if (!sessionData[sessionLogKey]) {
sessionData[sessionLogKey] = {
eventCount: sessionLogInfo.data.eventCount + 1,
addEventCount: 1,
uid: sessionLogInfo.data.uid,
createTime: sessionLogInfo.data.createTime
}
} else {
sessionData[sessionLogKey].eventCount++
sessionData[sessionLogKey].addEventCount++
}
// 事件
const eventInfo = await event.getEventAndCreate(params.ak, params.e_n)
// 填充数据
fillParams.push({
appid: params.ak,
version: params.v ? params.v : '',
platform: platform.getPlatformCode(params.ut, params.p),
channel: channel.getChannelCode(params),
device_id: params.did,
uid: params.uid ? params.uid : '',
session_id: sessionLogInfo.data.sessionLogId,
page_id: sessionLogInfo.data.pageId,
event_key: eventInfo.event_key,
param: params.e_v ? params.e_v : '',
// 版本
sdk_version: params.mpsdk ? params.mpsdk : '',
platform_version: params.mpv ? params.mpv : '',
// 设备相关
device_os_name: params.on ? params.on : platform.getOsName(params.p),
device_os_version: params.sv ? params.sv : '',
device_vendor: params.brand ? params.brand : '',
device_model: params.md ? params.md : '',
device_language: params.lang ? params.lang : '',
device_pixel_ratio: params.pr ? params.pr : '',
device_window_width: params.ww ? params.ww : '',
device_window_height: params.wh ? params.wh : '',
device_screen_width: params.sw ? params.sw : '',
device_screen_height: params.sh ? params.sh : '',
create_time: dateTime.getTime()
})
// 分享数据
if (eventInfo.event_key === 'share') {
shareParams.push(params)
}
}
if (fillParams.length === 0) {
return {
code: 200,
msg: 'Invild param'
}
}
if (shareParams.length > 0) {
const shareLog = new ShareLog()
await shareLog.fill(shareParams, this.sessionLogInfo)
}
const res = await this.insert(this.tableName, fillParams)
if (res && res.inserted) {
for (const sid in sessionData) {
await sessionLog.updateSession(sid, sessionData[sid])
}
return {
code: 0,
msg: 'success'
}
} else {
return {
code: 500,
msg: 'Filled error'
}
}
}
/**
* 事件日志清理
* @param {Number} days 保留天数
*/
async clean(days) {
days = Math.max(parseInt(days), 1)
console.log('clean event logs - day:', days)
const dateTime = new DateTime()
//删除过期数据
const res = await this.delete(this.tableName, {
create_time: {
$lt: dateTime.getTimeBySetDays(0 - days)
}
})
if (!res.code) {
console.log('clean event log:', res)
}
return res
}
}

@ -0,0 +1,268 @@
/**
* @class EventResult 事件结果统计
*/
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const Version = require('./version')
const EventLog = require('./eventLog')
const {
DateTime
} = require('../lib')
module.exports = class EventResult extends BaseMod {
constructor() {
super()
this.tableName = 'event-result'
this.platforms = []
this.channels = []
this.versions = []
}
/**
* 事件数据统计
* @param {String} type 统计类型 hour实时统计 day按天统计week按周统计 month按月统计
* @param {Date|Time} date 指定日期或时间戳
* @param {Boolean} reset 是否重置为ture时会重置该批次数据
*/
async stat(type, date, reset) {
const allowedType = ['day']
if (!allowedType.includes(type)) {
return {
code: 1002,
msg: 'This type is not allowed'
}
}
this.fillType = type
const dateTime = new DateTime()
const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
this.startTime = dateDimension.startTime
this.endTime = dateDimension.endTime
if (this.debug) {
console.log('dimension time', this.startTime + '--' + this.endTime)
}
// 查看当前时间段日志是否已存在,防止重复生成
if (!reset) {
const checkRes = await this.getCollection(this.tableName).where({
start_time: this.startTime,
end_time: this.endTime
}).get()
if (checkRes.data.length > 0) {
console.log('event log have existed')
return {
code: 1003,
msg: 'This log have existed'
}
}
} else {
const delRes = await this.delete(this.tableName, {
start_time: this.startTime,
end_time: this.endTime
})
console.log('delete old data result:', JSON.stringify(delRes))
}
// 数据获取
this.eventLog = new EventLog()
const statRes = await this.aggregate(this.eventLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
event_key: 1,
device_id: 1,
create_time: 1
},
match: {
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
},
group: {
_id: {
appid: '$appid',
version: '$version',
platform: '$platform',
channel: '$channel',
event_key: '$event_key'
},
event_count: {
$sum: 1
}
},
sort: {
event_count: 1
},
getAll: true
})
let res = {
code: 0,
msg: 'success'
}
if (this.debug) {
console.log('statRes', JSON.stringify(statRes))
}
if (statRes.data.length > 0) {
this.fillData = []
for (const i in statRes.data) {
await this.fill(statRes.data[i])
}
if (this.fillData.length > 0) {
res = await this.batchInsert(this.tableName, this.fillData)
}
}
return res
}
/**
* 事件统计数据填充
* @param {Object} data 数据集合
*/
async fill(data) {
// 平台信息
let platformInfo = null
if (this.platforms && this.platforms[data._id.platform]) {
//暂存下数据,减少读库
platformInfo = this.platforms[data._id.platform]
} else {
const platform = new Platform()
platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
if (!platformInfo || platformInfo.length === 0) {
platformInfo._id = ''
}
this.platforms[data._id.platform] = platformInfo
if (this.debug) {
console.log('platformInfo', JSON.stringify(platformInfo))
}
}
// 渠道信息
let channelInfo = null
const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
if (this.channels && this.channels[channelKey]) {
channelInfo = this.channels[channelKey]
} else {
const channel = new Channel()
channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
if (!channelInfo || channelInfo.length === 0) {
channelInfo._id = ''
}
this.channels[channelKey] = channelInfo
if (this.debug) {
console.log('channelInfo', JSON.stringify(channelInfo))
}
}
// 版本信息
let versionInfo = null
const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
if (this.versions && this.versions[versionKey]) {
versionInfo = this.versions[versionKey]
} else {
const version = new Version()
versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
if (!versionInfo || versionInfo.length === 0) {
versionInfo._id = ''
}
this.versions[versionKey] = versionInfo
if (this.debug) {
console.log('versionInfo', JSON.stringify(versionInfo))
}
}
const matchCondition = data._id
Object.assign(matchCondition, {
create_time: {
$gte: this.startTime,
$lte: this.endTime
}
})
if (this.debug) {
console.log('matchCondition', JSON.stringify(matchCondition))
}
// 触发事件设备数统计
const statEventDeviceRes = await this.aggregate(this.eventLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
event_key: 1,
device_id: 1,
create_time: 1
},
match: matchCondition,
group: [{
_id: {
device_id: '$device_id'
}
}, {
_id: {},
total_devices: {
$sum: 1
}
}]
})
let eventDeviceCount = 0
if (statEventDeviceRes.data.length > 0) {
eventDeviceCount = statEventDeviceRes.data[0].total_devices
}
// 触发事件用户数统计
const statEventUserRes = await this.aggregate(this.eventLog.tableName, {
project: {
appid: 1,
version: 1,
platform: 1,
channel: 1,
event_key: 1,
uid: 1,
create_time: 1
},
match: {
...matchCondition,
uid: {
$ne: ''
}
},
group: [{
_id: {
uid: '$uid'
}
}, {
_id: {},
total_users: {
$sum: 1
}
}]
})
let eventUserCount = 0
if (statEventUserRes.data.length > 0) {
eventUserCount = statEventUserRes.data[0].total_users
}
const datetime = new DateTime()
const insertParams = {
appid: data._id.appid,
platform_id: platformInfo._id,
channel_id: channelInfo._id,
version_id: versionInfo._id,
event_key: data._id.event_key,
event_count: data.event_count,
device_count: eventDeviceCount,
user_count: eventUserCount,
dimension: this.fillType,
stat_date: datetime.getDate('Ymd', this.startTime),
start_time: this.startTime,
end_time: this.endTime
}
this.fillData.push(insertParams)
return insertParams
}
}

@ -0,0 +1,23 @@
/**
* 基础对外模型
*/
module.exports = {
BaseMod: require('./base'),
SessionLog: require('./sessionLog'),
UserSessionLog: require('./userSessionLog'),
PageLog: require('./pageLog'),
EventLog: require('./eventLog'),
ShareLog: require('./shareLog'),
ErrorLog: require('./errorLog'),
AppCrashLogs: require('./appCrashLogs'),
StatResult: require('./statResult'),
ActiveUsers: require('./activeUsers'),
ActiveDevices: require('./activeDevices'),
PageResult: require('./pageResult'),
EventResult: require('./eventResult'),
ErrorResult: require('./errorResult'),
Loyalty: require('./loyalty'),
RunErrors: require('./runErrors'),
uniPay: require('./uni-pay'),
Setting: require('./setting'),
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save