├── .gitignore ├── .hbuilderx └── launch.json ├── App.vue ├── FILE_PREVIEW.md ├── LICENSE ├── PUSH_MESSAGE.md ├── README.md ├── api └── api.js ├── colorui ├── animation.css ├── components │ └── cu-custom.vue ├── icon.css └── main.css ├── common ├── css │ ├── common.css │ ├── custom-dark.less │ ├── custom-light.less │ ├── iconfont.css │ └── uni.css └── lib │ ├── md5.min.js │ └── u-charts.min.js ├── components ├── mescroll-uni │ ├── components │ │ ├── mescroll-down.css │ │ ├── mescroll-down.vue │ │ ├── mescroll-empty.vue │ │ ├── mescroll-top.vue │ │ ├── mescroll-up.css │ │ └── mescroll-up.vue │ ├── mescroll-body.css │ ├── mescroll-body.vue │ ├── mescroll-mixins.js │ ├── mescroll-uni-option.js │ ├── mescroll-uni.css │ ├── mescroll-uni.js │ ├── mescroll-uni.vue │ ├── mixins │ │ ├── mescroll-comp.js │ │ ├── mescroll-more-item.js │ │ └── mescroll-more.js │ └── wxs │ │ ├── mixins.js │ │ ├── renderjs.js │ │ └── wxs.wxs ├── tui │ └── tui-datetime.vue ├── uni-badge │ └── uni-badge.vue ├── uni-card │ └── uni-card.vue ├── uni-goods-nav │ └── uni-goods-nav.vue ├── uni-icons │ └── uni-icons.vue ├── uni-list-item │ └── uni-list-item.vue ├── uni-list │ └── uni-list.vue ├── uni-nav-bar │ └── uni-nav-bar.vue ├── uni-popup │ └── uni-popup.vue ├── uni-status-bar │ └── uni-status-bar.vue ├── uni-tag │ └── uni-tag.vue └── watch-login │ ├── css │ └── icon.css │ ├── watch-button.vue │ └── watch-input.vue ├── config └── index.js ├── hybrid └── html │ ├── README.md │ ├── index.html │ └── js │ ├── h5uploader.js │ └── signclient.js ├── i18n ├── en-US.js ├── index.js └── zh-CN.js ├── main.js ├── manifest.json ├── package.json ├── pages.json ├── pages ├── analysis │ ├── index.vue │ ├── time-table.vue │ ├── ucharts-demo-data.js │ └── ucharts-demo.vue ├── file │ ├── file-preview.vue │ └── file-upload.vue ├── index │ ├── audit-idea.vue │ ├── index.vue │ ├── project │ │ ├── adjust-project.vue │ │ ├── audit-project.vue │ │ └── detail-project.vue │ └── user │ │ ├── audit-user.vue │ │ └── detail-user.vue ├── login │ ├── css │ │ └── main.css │ ├── forget.vue │ ├── login.vue │ └── register.vue ├── project │ ├── index.vue │ ├── project-detail.vue │ └── project-list.vue ├── user │ ├── index.vue │ └── setting.vue └── yzcloud │ ├── index.vue │ ├── signclient.js │ ├── yz-edit.vue │ └── yz-preview-callback.vue ├── static ├── img │ ├── backtop.png │ ├── edit.png │ ├── logo.png │ ├── project │ │ ├── asset.png │ │ ├── build.png │ │ ├── land.png │ │ └── purchase.png │ ├── tabbar │ │ ├── home.png │ │ ├── homeHL.png │ │ ├── org.png │ │ ├── orgHL.png │ │ ├── project.png │ │ ├── projectHL.png │ │ ├── todo.png │ │ ├── todoHL.png │ │ ├── tongji.png │ │ ├── tongjiHL.png │ │ ├── user.png │ │ ├── userHL.png │ │ ├── yz.png │ │ ├── yz2.png │ │ ├── yzHL.png │ │ └── yzHL2.png │ └── user │ │ └── agencyOrg.png └── uni.ttf ├── store ├── index.js └── modules │ ├── app.js │ ├── index.js │ └── user.js ├── uni.scss ├── unipush离线集成指南.md ├── utils ├── MinCache.js ├── MinRequest.js ├── MinRouter.js ├── checkResponse.js ├── datetime.js ├── graceChecker.js ├── index.js └── url.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .project 3 | unpackage/ 4 | .vscode/ 5 | .idea 6 | .DS_Store 7 | *.iml 8 | -------------------------------------------------------------------------------- /.hbuilderx/launch.json: -------------------------------------------------------------------------------- 1 | { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ 2 | // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 3 | "version": "0.0", 4 | "configurations": [{ 5 | "type": "uniCloud", 6 | "default": { 7 | "launchtype": "remote" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /App.vue: -------------------------------------------------------------------------------- 1 | 139 | 140 | 149 | -------------------------------------------------------------------------------- /api/api.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import MinRequest from '@/utils/MinRequest' 3 | import globalConfig from '@/config' 4 | import { generateSign } from '@/pages/yzcloud/signclient.js' 5 | 6 | const minRequest = new MinRequest() 7 | 8 | // 请求拦截器 9 | minRequest.interceptors.request((request) => { 10 | return request 11 | }) 12 | 13 | // 响应拦截器 14 | minRequest.interceptors.response((response) => { 15 | return response.data 16 | }) 17 | 18 | // 设置默认配置 19 | minRequest.setConfig((config) => { 20 | config.baseURL = globalConfig.baseUrl 21 | return config 22 | }) 23 | 24 | export default { 25 | // 这里统一管理api请求 26 | apis: { 27 | login(params) { 28 | return minRequest.post('/post/user/login', params) 29 | }, 30 | userPwdModify(params) { 31 | return minRequest.post('/post/user/pwd/modify', params) 32 | }, 33 | // 项目审批列表 34 | listAuditProject() { 35 | return minRequest.get('/get/audit/project/list') 36 | }, 37 | // 用户审批列表 38 | listAuditUser() { 39 | return minRequest.get('/get/audit/user/list') 40 | }, 41 | // 文档管理接口:HTTP上传文件 42 | yzEditHttpUploadFile({ fileUrl }) { 43 | const sign = generateSign(globalConfig.yzEditAPPKEY, {"appId": [globalConfig.yzEditAPPID], 44 | "fileUrl": [fileUrl] 45 | }) 46 | return minRequest.post('/api/file/http', { 47 | fileUrl, 48 | appId: globalConfig.yzEditAPPID, 49 | sign 50 | }, { 51 | baseURL: globalConfig.yzDmcUrl 52 | }) 53 | }, 54 | // 格式转换接口 55 | yzConvertFile(params) { 56 | const sign = generateSign(globalConfig.yzFormatConvertAPPKEY, {"appId": [globalConfig.yzFormatConvertAPPID], 57 | "fileVersionId": [params.fileVersionId], 58 | "convertType": [params.convertType], 59 | "destinationName": [params.destinationName] 60 | }) 61 | return minRequest.post('/api/convert/file', { ...params, 62 | appId: globalConfig.yzFormatConvertAPPID, 63 | sign 64 | }, { 65 | baseURL: globalConfig.yzEicUrl, 66 | header: { 67 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8;' 68 | } 69 | }) 70 | } 71 | // 文档管理接口:上传文件 72 | // yzPreviewUploadFile({ file }) { 73 | // const sign = '' 74 | // return minRequest.post('/api/file/upload', { 75 | // file, 76 | // appId: globalConfig.yzPreviewAPPID, 77 | // sign 78 | // }, { 79 | // baseURL: globalConfig.yzDmcUrl 80 | // }) 81 | // }, 82 | // 永中云预览-在线预览 83 | // yzPreviewFile({ fileVersionId }) { 84 | // const sign = '' 85 | // return minRequest.get('/api/view/file', { 86 | // fileVersionId, 87 | // appId: globalConfig.yzPreviewAPPID, 88 | // sign 89 | // }, { 90 | // baseURL: globalConfig.yzEicUrl 91 | // }) 92 | // } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /colorui/animation.css: -------------------------------------------------------------------------------- 1 | /* 2 | Animation 微动画 3 | 基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 4 | */ 5 | 6 | /* css 滤镜 控制黑白底色gif的 */ 7 | .gif-black{ 8 | mix-blend-mode: screen; 9 | } 10 | .gif-white{ 11 | mix-blend-mode: multiply; 12 | } 13 | 14 | 15 | /* Animation css */ 16 | [class*=animation-] { 17 | animation-duration: .5s; 18 | animation-timing-function: ease-out; 19 | animation-fill-mode: both 20 | } 21 | 22 | .animation-fade { 23 | animation-name: fade; 24 | animation-duration: .8s; 25 | animation-timing-function: linear 26 | } 27 | 28 | .animation-scale-up { 29 | animation-name: scale-up 30 | } 31 | 32 | .animation-scale-down { 33 | animation-name: scale-down 34 | } 35 | 36 | .animation-slide-top { 37 | animation-name: slide-top 38 | } 39 | 40 | .animation-slide-bottom { 41 | animation-name: slide-bottom 42 | } 43 | 44 | .animation-slide-left { 45 | animation-name: slide-left 46 | } 47 | 48 | .animation-slide-right { 49 | animation-name: slide-right 50 | } 51 | 52 | .animation-shake { 53 | animation-name: shake 54 | } 55 | 56 | .animation-reverse { 57 | animation-direction: reverse 58 | } 59 | 60 | @keyframes fade { 61 | 0% { 62 | opacity: 0 63 | } 64 | 65 | 100% { 66 | opacity: 1 67 | } 68 | } 69 | 70 | @keyframes scale-up { 71 | 0% { 72 | opacity: 0; 73 | transform: scale(.2) 74 | } 75 | 76 | 100% { 77 | opacity: 1; 78 | transform: scale(1) 79 | } 80 | } 81 | 82 | @keyframes scale-down { 83 | 0% { 84 | opacity: 0; 85 | transform: scale(1.8) 86 | } 87 | 88 | 100% { 89 | opacity: 1; 90 | transform: scale(1) 91 | } 92 | } 93 | 94 | @keyframes slide-top { 95 | 0% { 96 | opacity: 0; 97 | transform: translateY(-100%) 98 | } 99 | 100 | 100% { 101 | opacity: 1; 102 | transform: translateY(0) 103 | } 104 | } 105 | 106 | @keyframes slide-bottom { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(100%) 110 | } 111 | 112 | 100% { 113 | opacity: 1; 114 | transform: translateY(0) 115 | } 116 | } 117 | 118 | @keyframes shake { 119 | 120 | 0%, 121 | 100% { 122 | transform: translateX(0) 123 | } 124 | 125 | 10% { 126 | transform: translateX(-9px) 127 | } 128 | 129 | 20% { 130 | transform: translateX(8px) 131 | } 132 | 133 | 30% { 134 | transform: translateX(-7px) 135 | } 136 | 137 | 40% { 138 | transform: translateX(6px) 139 | } 140 | 141 | 50% { 142 | transform: translateX(-5px) 143 | } 144 | 145 | 60% { 146 | transform: translateX(4px) 147 | } 148 | 149 | 70% { 150 | transform: translateX(-3px) 151 | } 152 | 153 | 80% { 154 | transform: translateX(2px) 155 | } 156 | 157 | 90% { 158 | transform: translateX(-1px) 159 | } 160 | } 161 | 162 | @keyframes slide-left { 163 | 0% { 164 | opacity: 0; 165 | transform: translateX(-100%) 166 | } 167 | 168 | 100% { 169 | opacity: 1; 170 | transform: translateX(0) 171 | } 172 | } 173 | 174 | @keyframes slide-right { 175 | 0% { 176 | opacity: 0; 177 | transform: translateX(100%) 178 | } 179 | 180 | 100% { 181 | opacity: 1; 182 | transform: translateX(0) 183 | } 184 | } -------------------------------------------------------------------------------- /colorui/components/cu-custom.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 66 | 67 | 70 | -------------------------------------------------------------------------------- /common/css/common.css: -------------------------------------------------------------------------------- 1 | page, uni-page-body, #app { 2 | height: 100%!important; 3 | } 4 | 5 | .w-h-100 { 6 | height: 100%; 7 | width: 100%; 8 | } 9 | 10 | .uni-flex { 11 | display: flex; 12 | } 13 | 14 | .uni-row { 15 | flex-direction: row; 16 | justify-content: flex-start; 17 | align-items: flex-start; 18 | margin: 4rpx 0; 19 | } 20 | 21 | .uni-row .flex-item-20 { 22 | color: #8799a3; 23 | } 24 | 25 | .uni-row .flex-item-30 { 26 | color: #8799a3; 27 | } 28 | 29 | .flex-item-20 { 30 | flex-basis: 20%; 31 | } 32 | 33 | .flex-item-80 { 34 | flex-basis: 80%; 35 | } 36 | 37 | .flex-item-30 { 38 | flex-basis: 30%; 39 | } 40 | 41 | .flex-item-70 { 42 | flex-basis: 70%; 43 | } 44 | 45 | .my-iconfont { 46 | font-size: 24rpx; 47 | } 48 | 49 | .footer-box { 50 | display: flex; 51 | justify-content: space-between; 52 | width: 100%; 53 | padding: 0 10%; 54 | } 55 | 56 | .footer-box__item { 57 | width: 100%; 58 | display: flex; 59 | align-items: center; 60 | } 61 | 62 | .footer-box__item:nth-child(2) { 63 | justify-content: center; 64 | } 65 | 66 | .footer-box__item:last-child { 67 | justify-content: flex-end; 68 | } 69 | 70 | .audit-card-content { 71 | margin: 0 20rpx; 72 | } 73 | 74 | .mycard .card-item { 75 | margin-bottom: 20rpx; 76 | } 77 | 78 | .mycard { 79 | margin-top: 20rpx; 80 | } 81 | 82 | .goods-carts { 83 | width: 100%; 84 | position: fixed; 85 | bottom: 0; 86 | } 87 | 88 | /* 顶部搜索框 */ 89 | .input-view { 90 | width: 100%; 91 | display: flex; 92 | background-color: #e7e7e7; 93 | height: 30px; 94 | border-radius: 15px; 95 | padding: 0 4%; 96 | flex-wrap: nowrap; 97 | margin: 7px 10rpx; 98 | line-height: 30px; 99 | background: #f5f5f5; 100 | } 101 | 102 | .input-view .uni-icon { 103 | line-height: 30px !important; 104 | } 105 | 106 | .input-view .input { 107 | height: 30px; 108 | line-height: 30px; 109 | width: 94%; 110 | padding: 0 3%; 111 | } 112 | 113 | .my-tab-bar .cu-item { 114 | height: auto; 115 | margin: 0; 116 | padding: 0; 117 | line-height: 44px; 118 | } 119 | 120 | /* 底部分享 */ 121 | .uni-share { 122 | background: #fff; 123 | } 124 | 125 | .uni-share-padding-bottom { 126 | padding-bottom: 22%; 127 | } 128 | 129 | .idea-textarea { 130 | border: 1rpx solid #d9d9d9; 131 | width: 100%; 132 | } 133 | 134 | .uni-share-title { 135 | line-height: 60rpx; 136 | font-size: 24rpx; 137 | padding: 15rpx 0; 138 | text-align: center; 139 | } 140 | 141 | .uni-share-content { 142 | display: flex; 143 | flex-wrap: wrap; 144 | padding: 15px 15px 0 15px; 145 | } 146 | 147 | .uni-share-content-box { 148 | display: flex; 149 | flex-direction: column; 150 | align-items: center; 151 | width: 25%; 152 | box-sizing: border-box; 153 | } 154 | 155 | .uni-share-content-image { 156 | display: flex; 157 | justify-content: center; 158 | align-items: center; 159 | width: 60rpx; 160 | height: 60rpx; 161 | overflow: hidden; 162 | border-radius: 10rpx; 163 | } 164 | 165 | .uni-share-content-image .image { 166 | width: 100%; 167 | height: 100%; 168 | } 169 | 170 | .uni-share-content-text { 171 | font-size: 26rpx; 172 | color: #333; 173 | padding-top: 5px; 174 | padding-bottom: 10px; 175 | } 176 | 177 | .uni-share-btn { 178 | height: 90rpx; 179 | line-height: 90rpx; 180 | border-top: 1px #f5f5f5 solid; 181 | text-align: center; 182 | color: #666; 183 | } 184 | 185 | .uni-timeline-item .uni-timeline-item-keynode { 186 | width: auto; 187 | } 188 | 189 | .uni-timeline-last-item .uni-timeline-item-divider { 190 | background-color: #bbb !important; 191 | } 192 | 193 | .uni-timeline-first-item .uni-timeline-item-divider { 194 | background-color: #1AAD19 !important; 195 | } 196 | 197 | .uni-card__header-extra-text { 198 | width: auto !important; 199 | } 200 | 201 | .scoll-y { 202 | height: 100%; 203 | } 204 | 205 | .tui-tabs-relative::before { 206 | border-bottom: none!important; 207 | } 208 | -------------------------------------------------------------------------------- /common/css/custom-dark.less: -------------------------------------------------------------------------------- 1 | @dark_bg_main: #0d1015; 2 | @dark_bg: #161a23; 3 | @dark_bg_sub: #2a2b2d; 4 | @dark_text_main: #d3e0f3; 5 | @dark_text_sub: #8c8c8c; 6 | @dark_text_3: #434343; 7 | @dark_text_4: #595959; 8 | 9 | .custom-dark { 10 | background-color: @dark_bg_main!important; 11 | .uni-card { 12 | background-color: @dark_bg!important; 13 | } 14 | 15 | .uni-row .flex-item-80, .uni-row .flex-item-70, .uni-card__header, .uni-card__header-extra-text, .qiun-title-dot-light { 16 | color: @dark_text_main!important; 17 | } 18 | 19 | .uni-card--shadow { 20 | border: 1px solid @dark_text_3!important; 21 | } 22 | 23 | .uni-card__footer { 24 | border-top: 1px solid @dark_text_3!important; 25 | } 26 | 27 | .analysis, .cu-bar, .qiun-columns, .qiun-bg-white, .detail-item { 28 | background-color: @dark_bg!important; 29 | color: @dark_text_main!important; 30 | 31 | .analysis-num { 32 | color: #fff566; 33 | } 34 | } 35 | 36 | .cu-list, .cu-item { 37 | background-color: @dark_bg!important; 38 | color: @dark_text_main!important; 39 | } 40 | .cu-item:after { 41 | border-bottom: 1px solid @dark_text_3!important; 42 | } 43 | 44 | .cu-item.arrow::before { 45 | color: @dark_text_4!important; 46 | } 47 | 48 | .grid-text { 49 | color: @dark_text_main!important; 50 | } 51 | 52 | .main-list { 53 | .cuIcon { 54 | color: @dark_text_main!important; 55 | } 56 | .uni-input-input { 57 | background-color: @dark_bg_sub!important; 58 | color: @dark_text_main!important; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /common/css/custom-light.less: -------------------------------------------------------------------------------- 1 | @light_bg: #ffffff; 2 | @light_text_main: #000000; 3 | @light_text_sub: #666666; 4 | 5 | .custom-light { 6 | .analysis, .cu-bar, .detail-item { 7 | background-color: @light_bg; 8 | color: @light_text_sub; 9 | 10 | .analysis-num { 11 | color: #1890ff; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-down.css: -------------------------------------------------------------------------------- 1 | /* 下拉刷新区域 */ 2 | .mescroll-downwarp { 3 | position: absolute; 4 | top: -100%; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | text-align: center; 9 | } 10 | 11 | /* 下拉刷新--内容区,定位于区域底部 */ 12 | .mescroll-downwarp .downwarp-content { 13 | position: absolute; 14 | left: 0; 15 | bottom: 0; 16 | width: 100%; 17 | min-height: 60rpx; 18 | padding: 20rpx 0; 19 | text-align: center; 20 | } 21 | 22 | /* 下拉刷新--提示文本 */ 23 | .mescroll-downwarp .downwarp-tip { 24 | display: inline-block; 25 | font-size: 28rpx; 26 | vertical-align: middle; 27 | margin-left: 16rpx; 28 | /* color: gray; 已在style设置color,此处删去*/ 29 | } 30 | 31 | /* 下拉刷新--旋转进度条 */ 32 | .mescroll-downwarp .downwarp-progress { 33 | display: inline-block; 34 | width: 32rpx; 35 | height: 32rpx; 36 | border-radius: 50%; 37 | border: 2rpx solid gray; 38 | border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ 39 | vertical-align: middle; 40 | } 41 | 42 | /* 旋转动画 */ 43 | .mescroll-downwarp .mescroll-rotate { 44 | animation: mescrollDownRotate 0.6s linear infinite; 45 | } 46 | 47 | @keyframes mescrollDownRotate { 48 | 0% { 49 | transform: rotate(0deg); 50 | } 51 | 52 | 100% { 53 | transform: rotate(360deg); 54 | } 55 | } -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-down.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-empty.vue: -------------------------------------------------------------------------------- 1 | 8 | 15 | 16 | 48 | 49 | 91 | -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-top.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 49 | 50 | 84 | -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-up.css: -------------------------------------------------------------------------------- 1 | /* 上拉加载区域 */ 2 | .mescroll-upwarp { 3 | box-sizing: border-box; 4 | min-height: 110rpx; 5 | padding: 30rpx 0; 6 | text-align: center; 7 | clear: both; 8 | } 9 | 10 | /*提示文本 */ 11 | .mescroll-upwarp .upwarp-tip, 12 | .mescroll-upwarp .upwarp-nodata { 13 | display: inline-block; 14 | font-size: 28rpx; 15 | vertical-align: middle; 16 | /* color: gray; 已在style设置color,此处删去*/ 17 | } 18 | 19 | .mescroll-upwarp .upwarp-tip { 20 | margin-left: 16rpx; 21 | } 22 | 23 | /*旋转进度条 */ 24 | .mescroll-upwarp .upwarp-progress { 25 | display: inline-block; 26 | width: 32rpx; 27 | height: 32rpx; 28 | border-radius: 50%; 29 | border: 2rpx solid gray; 30 | border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ 31 | vertical-align: middle; 32 | } 33 | 34 | /* 旋转动画 */ 35 | .mescroll-upwarp .mescroll-rotate { 36 | animation: mescrollUpRotate 0.6s linear infinite; 37 | } 38 | 39 | @keyframes mescrollUpRotate { 40 | 0% { 41 | transform: rotate(0deg); 42 | } 43 | 44 | 100% { 45 | transform: rotate(360deg); 46 | } 47 | } -------------------------------------------------------------------------------- /components/mescroll-uni/components/mescroll-up.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /components/mescroll-uni/mescroll-body.css: -------------------------------------------------------------------------------- 1 | .mescroll-body { 2 | position: relative; /* 下拉刷新区域相对自身定位 */ 3 | height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/ 4 | overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */ 5 | box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ 6 | } 7 | 8 | /* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */ 9 | .mescroll-body.mescorll-sticky{ 10 | overflow: unset !important 11 | } 12 | 13 | /* 适配 iPhoneX */ 14 | @supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { 15 | .mescroll-safearea { 16 | padding-bottom: constant(safe-area-inset-bottom); 17 | padding-bottom: env(safe-area-inset-bottom); 18 | } 19 | } -------------------------------------------------------------------------------- /components/mescroll-uni/mescroll-mixins.js: -------------------------------------------------------------------------------- 1 | // mescroll-body 和 mescroll-uni 通用 2 | 3 | // import MescrollUni from "./mescroll-uni.vue"; 4 | // import MescrollBody from "./mescroll-body.vue"; 5 | 6 | const MescrollMixin = { 7 | // components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册 8 | // MescrollUni, 9 | // MescrollBody 10 | // }, 11 | data() { 12 | return { 13 | mescroll: null //mescroll实例对象 14 | } 15 | }, 16 | // 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例) 17 | onPullDownRefresh(){ 18 | this.mescroll && this.mescroll.onPullDownRefresh(); 19 | }, 20 | // 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效) 21 | onPageScroll(e) { 22 | this.mescroll && this.mescroll.onPageScroll(e); 23 | }, 24 | // 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效) 25 | onReachBottom() { 26 | this.mescroll && this.mescroll.onReachBottom(); 27 | }, 28 | methods: { 29 | // mescroll组件初始化的回调,可获取到mescroll对象 30 | mescrollInit(mescroll) { 31 | this.mescroll = mescroll; 32 | this.mescrollInitByRef(); // 兼容字节跳动小程序 33 | }, 34 | // 以ref的方式初始化mescroll对象 (兼容字节跳动小程序) 35 | mescrollInitByRef() { 36 | if(!this.mescroll || !this.mescroll.resetUpScroll){ 37 | let mescrollRef = this.$refs.mescrollRef; 38 | if(mescrollRef) this.mescroll = mescrollRef.mescroll 39 | } 40 | }, 41 | // 下拉刷新的回调 (mixin默认resetUpScroll) 42 | downCallback() { 43 | if(this.mescroll.optUp.use){ 44 | this.mescroll.resetUpScroll() 45 | }else{ 46 | setTimeout(()=>{ 47 | this.mescroll.endSuccess(); 48 | }, 500) 49 | } 50 | }, 51 | // 上拉加载的回调 52 | upCallback() { 53 | // mixin默认延时500自动结束加载 54 | setTimeout(()=>{ 55 | this.mescroll.endErr(); 56 | }, 500) 57 | } 58 | }, 59 | mounted() { 60 | this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况 61 | } 62 | 63 | } 64 | 65 | export default MescrollMixin; 66 | -------------------------------------------------------------------------------- /components/mescroll-uni/mescroll-uni-option.js: -------------------------------------------------------------------------------- 1 | // 全局配置 2 | // mescroll-body 和 mescroll-uni 通用 3 | const GlobalOption = { 4 | down: { 5 | // 其他down的配置参数也可以写,这里只展示了常用的配置: 6 | textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本 7 | textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本 8 | textLoading: '加载中 ...', // 加载中的提示文本 9 | textSuccess: '加载成功', // 加载成功的文本 10 | textErr: '加载失败', // 加载失败的文本 11 | beforeEndDelay: 100, // 延时结束的时长 (显示加载成功/失败的时长) 12 | offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调 13 | native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例) 14 | }, 15 | up: { 16 | // 其他up的配置参数也可以写,这里只展示了常用的配置: 17 | textLoading: '加载中 ...', // 加载中的提示文本 18 | textNoMore: '-- END --', // 没有更多数据的提示文本 19 | offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance ) 20 | toTop: { 21 | // 回到顶部按钮,需配置src才显示 22 | src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png ) 23 | offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px 24 | right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) 25 | bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) 26 | width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx) 27 | }, 28 | empty: { 29 | use: true, // 是否显示空布局 30 | icon: "https://www.mescroll.com/img/mescroll-empty.png", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png ) 31 | tip: '~ 空空如也 ~' // 提示 32 | } 33 | } 34 | } 35 | 36 | export default GlobalOption 37 | -------------------------------------------------------------------------------- /components/mescroll-uni/mescroll-uni.css: -------------------------------------------------------------------------------- 1 | .mescroll-uni-warp{ 2 | height: 100%; 3 | } 4 | 5 | .mescroll-uni-content{ 6 | height: 100%; 7 | } 8 | 9 | .mescroll-uni { 10 | position: relative; 11 | width: 100%; 12 | height: 100%; 13 | min-height: 200rpx; 14 | overflow-y: auto; 15 | box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ 16 | } 17 | 18 | /* 定位的方式固定高度 */ 19 | .mescroll-uni-fixed{ 20 | z-index: 1; 21 | position: fixed; 22 | top: 0; 23 | left: 0; 24 | right: 0; 25 | bottom: 0; 26 | width: auto; /* 使right生效 */ 27 | height: auto; /* 使bottom生效 */ 28 | } 29 | 30 | /* 适配 iPhoneX */ 31 | @supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { 32 | .mescroll-safearea { 33 | padding-bottom: constant(safe-area-inset-bottom); 34 | padding-bottom: env(safe-area-inset-bottom); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /components/mescroll-uni/mixins/mescroll-comp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期 3 | */ 4 | const MescrollCompMixin = { 5 | // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级) 6 | onPageScroll(e) { 7 | this.handlePageScroll(e) 8 | }, 9 | onReachBottom() { 10 | this.handleReachBottom() 11 | }, 12 | // 当down的native: true时, 还需传递此方法进到子组件 13 | onPullDownRefresh(){ 14 | this.handlePullDownRefresh() 15 | }, 16 | // mescroll-body写在子子子...组件的情况 (多级) 17 | data() { 18 | return { 19 | mescroll: { 20 | onPageScroll: e=>{ 21 | this.handlePageScroll(e) 22 | }, 23 | onReachBottom: ()=>{ 24 | this.handleReachBottom() 25 | }, 26 | onPullDownRefresh: ()=>{ 27 | this.handlePullDownRefresh() 28 | } 29 | } 30 | } 31 | }, 32 | methods:{ 33 | handlePageScroll(e){ 34 | let item = this.$refs["mescrollItem"]; 35 | if(item && item.mescroll) item.mescroll.onPageScroll(e); 36 | }, 37 | handleReachBottom(){ 38 | let item = this.$refs["mescrollItem"]; 39 | if(item && item.mescroll) item.mescroll.onReachBottom(); 40 | }, 41 | handlePullDownRefresh(){ 42 | let item = this.$refs["mescrollItem"]; 43 | if(item && item.mescroll) item.mescroll.onPullDownRefresh(); 44 | } 45 | } 46 | } 47 | 48 | export default MescrollCompMixin; 49 | -------------------------------------------------------------------------------- /components/mescroll-uni/mixins/mescroll-more-item.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例) 3 | */ 4 | const MescrollMoreItemMixin = { 5 | // 支付宝小程序不支持props的mixin,需写在具体的页面中 6 | // #ifndef MP-ALIPAY || MP-DINGTALK 7 | props:{ 8 | i: Number, // 每个tab页的专属下标 9 | index: { // 当前tab的下标 10 | type: Number, 11 | default(){ 12 | return 0 13 | } 14 | } 15 | }, 16 | // #endif 17 | data() { 18 | return { 19 | downOption:{ 20 | auto:false // 不自动加载 21 | }, 22 | upOption:{ 23 | auto:false // 不自动加载 24 | }, 25 | isInit: false // 当前tab是否已初始化 26 | } 27 | }, 28 | watch:{ 29 | // 监听下标的变化 30 | index(val){ 31 | if (this.i === val && !this.isInit) { 32 | this.isInit = true; // 标记为true 33 | this.mescroll && this.mescroll.triggerDownScroll(); 34 | } 35 | } 36 | }, 37 | methods: { 38 | // 以ref的方式初始化mescroll对象 (兼容字节跳动小程序) 39 | mescrollInitByRef() { 40 | if(!this.mescroll || !this.mescroll.resetUpScroll){ 41 | // 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标' 42 | let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i]; 43 | if(mescrollRef) this.mescroll = mescrollRef.mescroll 44 | } 45 | }, 46 | // mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit) 47 | mescrollInit(mescroll) { 48 | this.mescroll = mescroll; 49 | this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 50 | // 自动加载当前tab的数据 51 | if(this.i === this.index){ 52 | this.isInit = true; // 标记为true 53 | this.mescroll.triggerDownScroll(); 54 | } 55 | }, 56 | } 57 | } 58 | 59 | export default MescrollMoreItemMixin; 60 | -------------------------------------------------------------------------------- /components/mescroll-uni/mixins/mescroll-more.js: -------------------------------------------------------------------------------- 1 | /** 2 | * mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期 3 | */ 4 | const MescrollMoreMixin = { 5 | data() { 6 | return { 7 | tabIndex: 0, // 当前tab下标 8 | mescroll: { 9 | onPageScroll: e=>{ 10 | this.handlePageScroll(e) 11 | }, 12 | onReachBottom: ()=>{ 13 | this.handleReachBottom() 14 | }, 15 | onPullDownRefresh: ()=>{ 16 | this.handlePullDownRefresh() 17 | } 18 | } 19 | } 20 | }, 21 | // 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 22 | onPageScroll(e) { 23 | this.handlePageScroll(e) 24 | }, 25 | onReachBottom() { 26 | this.handleReachBottom() 27 | }, 28 | // 当down的native: true时, 还需传递此方法进到子组件 29 | onPullDownRefresh(){ 30 | this.handlePullDownRefresh() 31 | }, 32 | methods:{ 33 | handlePageScroll(e){ 34 | let mescroll = this.getMescroll(this.tabIndex); 35 | mescroll && mescroll.onPageScroll(e); 36 | }, 37 | handleReachBottom(){ 38 | let mescroll = this.getMescroll(this.tabIndex); 39 | mescroll && mescroll.onReachBottom(); 40 | }, 41 | handlePullDownRefresh(){ 42 | let mescroll = this.getMescroll(this.tabIndex); 43 | mescroll && mescroll.onPullDownRefresh(); 44 | }, 45 | // 根据下标获取对应子组件的mescroll 46 | getMescroll(i){ 47 | if(!this.mescrollItems) this.mescrollItems = []; 48 | if(!this.mescrollItems[i]) { 49 | // v-for中的refs 50 | let vForItem = this.$refs["mescrollItem"]; 51 | if(vForItem){ 52 | this.mescrollItems[i] = vForItem[i] 53 | }else{ 54 | // 普通的refs,不可重复 55 | this.mescrollItems[i] = this.$refs["mescrollItem"+i]; 56 | } 57 | } 58 | let item = this.mescrollItems[i] 59 | return item ? item.mescroll : null 60 | }, 61 | // 切换tab,恢复滚动条位置 62 | tabChange(i){ 63 | let mescroll = this.getMescroll(i); 64 | if(mescroll){ 65 | // 延时(比$nextTick靠谱一些),确保元素已渲染 66 | setTimeout(()=>{ 67 | mescroll.scrollTo(mescroll.getScrollTop(),0) 68 | },30) 69 | } 70 | } 71 | } 72 | } 73 | 74 | export default MescrollMoreMixin; 75 | -------------------------------------------------------------------------------- /components/mescroll-uni/wxs/mixins.js: -------------------------------------------------------------------------------- 1 | // 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信 2 | const WxsMixin = { 3 | data() { 4 | return { 5 | // 传入wxs视图层的数据 (响应式) 6 | wxsProp: { 7 | optDown:{}, // 下拉刷新的配置 8 | scrollTop:0, // 滚动条的距离 9 | bodyHeight:0, // body的高度 10 | isDownScrolling:false, // 是否正在下拉刷新中 11 | isUpScrolling:false, // 是否正在上拉加载中 12 | isScrollBody:true, // 是否为mescroll-body滚动 13 | isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新 14 | t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer) 15 | }, 16 | 17 | // 标记调用wxs视图层的方法 18 | callProp: { 19 | callType: '', // 方法名 20 | t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer) 21 | }, 22 | 23 | // 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs) 24 | // #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5 25 | wxsBiz: { 26 | //注册列表touchstart事件,用于下拉刷新 27 | touchstartEvent: e=> { 28 | this.mescroll.touchstartEvent(e); 29 | }, 30 | //注册列表touchmove事件,用于下拉刷新 31 | touchmoveEvent: e=> { 32 | this.mescroll.touchmoveEvent(e); 33 | }, 34 | //注册列表touchend事件,用于下拉刷新 35 | touchendEvent: e=> { 36 | this.mescroll.touchendEvent(e); 37 | }, 38 | propObserver(){}, // 抹平wxs的写法 39 | callObserver(){} // 抹平wxs的写法 40 | }, 41 | // #endif 42 | 43 | // 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js) 44 | // #ifndef APP-PLUS || H5 45 | renderBiz: { 46 | propObserver(){} // 抹平renderjs的写法 47 | } 48 | // #endif 49 | } 50 | }, 51 | methods: { 52 | // wxs视图层调用逻辑层的回调 53 | wxsCall(msg){ 54 | if(msg.type === 'setWxsProp'){ 55 | // 更新wxsProp数据 (值改变才触发更新) 56 | this.wxsProp = { 57 | optDown: this.mescroll.optDown, 58 | scrollTop: this.mescroll.getScrollTop(), 59 | bodyHeight: this.mescroll.getBodyHeight(), 60 | isDownScrolling: this.mescroll.isDownScrolling, 61 | isUpScrolling: this.mescroll.isUpScrolling, 62 | isUpBoth: this.mescroll.optUp.isBoth, 63 | isScrollBody:this.mescroll.isScrollBody, 64 | t: Date.now() 65 | } 66 | }else if(msg.type === 'setLoadType'){ 67 | // 设置inOffset,outOffset的状态 68 | this.downLoadType = msg.downLoadType 69 | // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中 70 | this.$set(this.mescroll, 'downLoadType', this.downLoadType) 71 | // 重置是否加载成功的状态 72 | this.$set(this.mescroll, 'isDownEndSuccess', null) 73 | }else if(msg.type === 'triggerDownScroll'){ 74 | // 主动触发下拉刷新 75 | this.mescroll.triggerDownScroll(); 76 | }else if(msg.type === 'endDownScroll'){ 77 | // 结束下拉刷新 78 | this.mescroll.endDownScroll(); 79 | }else if(msg.type === 'triggerUpScroll'){ 80 | // 主动触发上拉加载 81 | this.mescroll.triggerUpScroll(true); 82 | } 83 | } 84 | }, 85 | mounted() { 86 | // #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 87 | // 配置主动触发wxs显示加载进度的回调 88 | this.mescroll.optDown.afterLoading = ()=>{ 89 | this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) 90 | } 91 | // 配置主动触发wxs隐藏加载进度的回调 92 | this.mescroll.optDown.afterEndDownScroll = ()=>{ 93 | this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) 94 | let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0) 95 | setTimeout(()=>{ 96 | if(this.downLoadType === 4 || this.downLoadType === 0){ 97 | this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新) 98 | } 99 | // 状态挂载到mescroll对象, 以便在其他组件中使用, 比如中 100 | this.$set(this.mescroll, 'downLoadType', this.downLoadType) 101 | }, delay) 102 | } 103 | // 初始化wxs的数据 104 | this.wxsCall({type: 'setWxsProp'}) 105 | // #endif 106 | } 107 | } 108 | 109 | export default WxsMixin; 110 | -------------------------------------------------------------------------------- /components/mescroll-uni/wxs/renderjs.js: -------------------------------------------------------------------------------- 1 | // 使用renderjs直接操作window对象,实现动态控制app和h5的bounce 2 | // bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止) 3 | // https://uniapp.dcloud.io/frame?id=renderjs 4 | 5 | // 与wxs的me实例一致 6 | var me = {} 7 | 8 | // 初始化window对象的touch事件 (仅初始化一次) 9 | if(window && !window.$mescrollRenderInit){ 10 | window.$mescrollRenderInit = true 11 | 12 | 13 | window.addEventListener('touchstart', function(e){ 14 | if (me.disabled()) return; 15 | me.startPoint = me.getPoint(e); // 记录起点 16 | }, {passive: true}) 17 | 18 | 19 | window.addEventListener('touchmove', function(e){ 20 | if (me.disabled()) return; 21 | if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce 22 | 23 | var curPoint = me.getPoint(e); // 当前点 24 | var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 25 | // 向下拉 26 | if (moveY > 0) { 27 | // 可下拉的条件 28 | if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) { 29 | 30 | // 只有touch在mescroll的view上面,才禁止bounce 31 | var el = e.target; 32 | var isMescrollTouch = false; 33 | while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") { 34 | var cls = el.classList; 35 | if (cls && cls.contains('mescroll-render-touch')) { 36 | isMescrollTouch = true 37 | break; 38 | } 39 | el = el.parentNode; // 继续检查其父元素 40 | } 41 | // 禁止bounce (不会对swiper和iOS侧滑返回造成影响) 42 | if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault(); 43 | } 44 | } 45 | }, {passive: false}) 46 | } 47 | 48 | /* 获取滚动条的位置 */ 49 | me.getScrollTop = function() { 50 | return me.scrollTop || 0 51 | } 52 | 53 | /* 是否禁用下拉刷新 */ 54 | me.disabled = function(){ 55 | return !me.optDown || !me.optDown.use || me.optDown.native 56 | } 57 | 58 | /* 根据点击滑动事件获取第一个手指的坐标 */ 59 | me.getPoint = function(e) { 60 | if (!e) { 61 | return {x: 0,y: 0} 62 | } 63 | if (e.touches && e.touches[0]) { 64 | return {x: e.touches[0].pageX,y: e.touches[0].pageY} 65 | } else if (e.changedTouches && e.changedTouches[0]) { 66 | return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} 67 | } else { 68 | return {x: e.clientX,y: e.clientY} 69 | } 70 | } 71 | 72 | /** 73 | * 监听逻辑层数据的变化 (实时更新数据) 74 | */ 75 | function propObserver(wxsProp) { 76 | me.optDown = wxsProp.optDown 77 | me.scrollTop = wxsProp.scrollTop 78 | me.isDownScrolling = wxsProp.isDownScrolling 79 | me.isUpScrolling = wxsProp.isUpScrolling 80 | me.isUpBoth = wxsProp.isUpBoth 81 | } 82 | 83 | /* 导出模块 */ 84 | const renderBiz = { 85 | data() { 86 | return { 87 | propObserver: propObserver, 88 | } 89 | } 90 | } 91 | 92 | export default renderBiz; -------------------------------------------------------------------------------- /components/uni-badge/uni-badge.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 34 | 35 | 102 | -------------------------------------------------------------------------------- /components/uni-card/uni-card.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 106 | 107 | 291 | -------------------------------------------------------------------------------- /components/uni-goods-nav/uni-goods-nav.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 100 | 101 | 198 | -------------------------------------------------------------------------------- /components/uni-list-item/uni-list-item.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 140 | 141 | 246 | -------------------------------------------------------------------------------- /components/uni-list/uni-list.vue: -------------------------------------------------------------------------------- 1 | 6 | 11 | 46 | -------------------------------------------------------------------------------- /components/uni-nav-bar/uni-nav-bar.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 133 | 134 | 225 | -------------------------------------------------------------------------------- /components/uni-popup/uni-popup.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 95 | 207 | -------------------------------------------------------------------------------- /components/uni-status-bar/uni-status-bar.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 27 | -------------------------------------------------------------------------------- /components/uni-tag/uni-tag.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 69 | 70 | 164 | -------------------------------------------------------------------------------- /components/watch-login/watch-button.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 41 | 42 | 144 | -------------------------------------------------------------------------------- /components/watch-login/watch-input.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 162 | 163 | 205 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ip地址或域名 3 | */ 4 | const ipAddress = 'http://113.62.127.199:38080/app/mock/16' 5 | // 文件访问地址 6 | const fileAddr = 'http://localhost:8082/fileUpload/' 7 | 8 | // 永中云服务 9 | // 文档管理接口url 10 | const yzDmc = 'http://dmc.yozocloud.cn' 11 | const yzEic = 'http://eic.yozocloud.cn' 12 | // 云预览应用信息 13 | const yzPreviewAPPID = 'appId' 14 | const yzPreviewAPPKEY = 'appKey' 15 | // 云编辑应用信息 16 | const yzEditAPPID = 'appId' 17 | const yzEditAPPKEY = 'appKey' 18 | // 格式转换应用信息 19 | const yzFormatConvertAPPID = 'appId' 20 | const yzFormatConvertAPPKEY = 'appKey' 21 | /** 22 | * api前缀 23 | */ 24 | const apiPrefix = '/apiUA' 25 | const apiYzDmc = '/apiYzDmc' 26 | const apiYzEic = '/apiYzEic' 27 | /** 28 | * 针对不同平台的baseUrl 29 | */ 30 | const getBaseUrl = () => { 31 | // #ifdef H5 32 | return apiPrefix 33 | // #endif 34 | // #ifndef H5 35 | return ipAddress 36 | // #endif 37 | } 38 | const getYzDmc = () => { 39 | // #ifdef H5 40 | return apiYzDmc 41 | // #endif 42 | // #ifndef H5 43 | return yzDmc 44 | // #endif 45 | } 46 | const getYzEic = () => { 47 | // #ifdef H5 48 | return apiYzEic 49 | // #endif 50 | // #ifndef H5 51 | return yzEic 52 | // #endif 53 | } 54 | export default { 55 | /** 56 | * 针对不同平台的baseUrl 57 | */ 58 | baseUrl: getBaseUrl(), 59 | fileAddr, 60 | yzDmcUrl: getYzDmc(), 61 | yzEic, 62 | yzEicUrl: getYzEic(), 63 | yzPreviewAPPID, 64 | yzPreviewAPPKEY, 65 | yzEditAPPID, 66 | yzEditAPPKEY, 67 | yzFormatConvertAPPID, 68 | yzFormatConvertAPPKEY 69 | } 70 | -------------------------------------------------------------------------------- /hybrid/html/README.md: -------------------------------------------------------------------------------- 1 | ## web-view加载本地html 2 | > 参考https://uniapp.dcloud.io/component/web-view -------------------------------------------------------------------------------- /hybrid/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | H5文档上传 7 | 8 | 9 | 10 |

H5文档上传

11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /hybrid/html/js/h5uploader.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * h5uploader.js 3 | * This is a simple file upload plugin depends on HTML5. 4 | * You can use it to mordern browser. 5 | * 6 | * version: 1.0 7 | * Copyright 2015, Ziv | http://imziv.com 8 | * Released under the MIT License 9 | * https://github.com/wewoor/h5uploader 10 | **/ 11 | 12 | (function(window) { 13 | 14 | window.H5Uploader = (function() { 15 | 16 | function createXhr() { 17 | if (typeof XMLHttpRequest != "undefined") { 18 | return new XMLHttpRequest(); 19 | } else if (typeof ActiveXObject != "undefined") { 20 | if (typeof arguments.callee.activeXString != "string") { 21 | var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"]; 22 | for (var i = 0, len = versions.length; i < len; i++) { 23 | try { 24 | new ActiveXObject(versions[i]); 25 | arguments.callee.activeXString = versions[i]; 26 | break; 27 | } catch (e) { 28 | throw new Error('Create XHR ActiveXObject error.' + e); 29 | } 30 | } 31 | } 32 | return new ActiveXObject(arguments.callee.activeXString); 33 | } else throw new Error('No XHR object avaliable.' + e); 34 | } 35 | 36 | return { 37 | upload: function(literals) { 38 | if (Object.prototype.toString.call(literals) !== '[object Array]') { 39 | this.handUpload(literals); 40 | } else { 41 | for (var i = 0; i < literals.length; i++) { 42 | this.handUpload(literals[i]); 43 | } 44 | } 45 | }, 46 | 47 | handUpload: function(literals) { 48 | 49 | if (literals.action === undefined) { 50 | throw new Error('The upload action address option is undefined.'); 51 | } 52 | 53 | var xhr = createXhr(); 54 | xhr.open("POST", literals.action, true); 55 | // xhr.setRequestHeader("Content-Type", "multipart/form-data"); 56 | // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 57 | xhr.onreadystatechange = function() { 58 | if (xhr.readyState == 4) { 59 | var body = xhr.responseText; 60 | if (xhr.status >= 200 && xhr.status < 300 || 61 | xhr.status == 304) { 62 | if (literals.success) { 63 | literals.success(body); 64 | } 65 | } else { 66 | if (literals.fail) { 67 | literals.fail(body); 68 | } 69 | } 70 | } 71 | }; 72 | 73 | if (!literals.id) { 74 | throw new Error('The upload id option is undefined.'); 75 | } 76 | // var myForm = document.getElementById(literals.formId); 77 | var data = new FormData(); 78 | var file = document.getElementById(literals.id); 79 | if (!file) { 80 | throw new Error('The upload file element is undefined::id:' + literals.id); 81 | } 82 | var name = file.getAttribute('name'); 83 | if (!name) { 84 | throw new Error('The upload file input name is undefined.'); 85 | } 86 | 87 | if (literals.size) { // Check file Size 88 | var evt = this.checkSize(file.files, literals.size.max); 89 | if (evt) { 90 | if (literals.size.valide) literals.size.valide(evt); 91 | throw new Error('The upload file size exceed max value.'); 92 | } 93 | } 94 | 95 | if (literals.type) { // Check file type 96 | var evt1 = this.checkType(file.files, literals.type.name); 97 | if (evt1) { 98 | if (literals.type.valide) literals.type.valide(evt1); 99 | throw new Error('The upload file type is error.'); 100 | } 101 | } 102 | 103 | if (literals.progress) { // Progress 104 | literals.progress(); 105 | } 106 | 107 | for (var i = 0; i < file.files.length; i++) { 108 | data.append(name, file.files[i]); 109 | } 110 | 111 | try { 112 | xhr.send(data); 113 | } catch (e) { 114 | throw new Error(e); 115 | } 116 | }, 117 | 118 | // Validate file size 119 | checkSize: function(file, size) { 120 | for (var i = 0; i < file.length; i++) { 121 | if (file[i].size > size * 1024) { // bytes 122 | return file[i]; 123 | } 124 | } 125 | }, 126 | 127 | // Validate file type 128 | checkType: function(file, type) { 129 | for (var i = 0; i < file.length; i++) { 130 | var arr = file[i].name.split("."); 131 | if (type.indexOf(arr[arr.length - 1]) === -1) { 132 | return file[i]; 133 | } 134 | } 135 | } 136 | }; 137 | })(); 138 | 139 | })(window); 140 | -------------------------------------------------------------------------------- /hybrid/html/js/signclient.js: -------------------------------------------------------------------------------- 1 | function generateSign(secret, params) { 2 | var fullParamStr = uniqSortParams(params); 3 | return hmacSHA256(fullParamStr, secret); 4 | } 5 | 6 | function uniqSortParams(params) { 7 | delete params.sign; 8 | 9 | var var5 = []; 10 | var var6 = 0; 11 | for (var key in params) { 12 | var5[var6] = key; 13 | var6++; 14 | } 15 | var5.sort(function (a, b) { 16 | return a.localeCompare(b, 'zh-CN'); 17 | }); 18 | 19 | var result = ""; 20 | for (var var7 = 0; var7 < var5.length; var7++) { 21 | var key = var5[var7] 22 | var var8 = params[key]; 23 | var8.sort(function (a, b) { 24 | return a.localeCompare(b, 'zh-CN'); 25 | }); 26 | 27 | if (var8 != null && var8.length > 0) { 28 | for (var var9 = 0; var9 < var8.length; var9++) { 29 | result += key + "=" + var8[var9]; 30 | } 31 | } else { 32 | result += key + "="; 33 | } 34 | } 35 | return result; 36 | } 37 | 38 | function hmacSHA256(data, key) { 39 | data != null ? data : ""; 40 | var var2 = CryptoJS.HmacSHA256(data, key); 41 | var var3 = var2.toString(CryptoJS.enc.Hex); 42 | return var3.toUpperCase(); 43 | } 44 | -------------------------------------------------------------------------------- /i18n/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'ProjectApproval': 'Project Approval', 3 | 'ProjectAdjust': 'Project Adjust', 4 | 'UserApproval': 'User Approval', 5 | 'FilePreview': 'File Previewing', 6 | 'Project': 'Project', 7 | 'YzCloud': 'Yozo Cloud', 8 | 'YzDocPreview': 'Yozo Document Preview', 9 | 'YzDocEdit': 'Yozo Document Edit', 10 | 'YzDocPreviewEdit': 'Yozo Document Preview/Edit', 11 | 'DocUpload': 'Document Upload', 12 | 'ProjectDetail': 'Project in detail', 13 | 'Statistics': 'Statistics', 14 | 'Profile': 'Profile', 15 | 'ToDo': 'ToDo', 16 | 'Language': 'Language', 17 | 'Empty': 'Empty', 18 | 'HardLoading': 'Hard loading', 19 | 'NoMore': 'No more', 20 | 'BasicInfo': 'Basic information', 21 | 'Ok': 'Ok', 22 | 'ClearSearchHistory': 'Clear search history', 23 | 'Settings': 'Settings', 24 | 'SignOut': 'Sign out', 25 | 'ChangePassword': 'Change password', 26 | 'Upgrade': 'Upgrade', 27 | 'UserName': 'UserName', 28 | 'Name': 'Name', 29 | 'Role': 'Role', 30 | 'RegisteredUser': 'Registered user', 31 | 'ProjectThisMonth': 'Project this month', 32 | 'System': 'System', 33 | 'English': 'English', 34 | 'Chinese': 'Chinese', 35 | 'en': 'English', 36 | 'zh-CN': 'Chinese', 37 | 'Theme': 'Theme', 38 | 'BluishGreen': 'bluish green', 39 | 'Blush': 'blush', 40 | 'Volcano': 'volcano', 41 | 'Orange': 'Orange', 42 | 'IndigoBlue': 'indigo-blue', 43 | 'Indigo': 'indigo', 44 | 'Verdant': 'verdant', 45 | 'Water': 'water', 46 | 'Grey': 'grey', 47 | 'PinkGold': 'pink gold', 48 | 'Dim': 'dim', 49 | 'Verdigris': 'verdigris', 50 | 'DeepBlack': 'deep black', 51 | 'AgateGreen': 'agate green', 52 | 'DarkGreen': 'dark green', 53 | 'DarkMode': 'dark mode', 54 | 'Dark': 'Dark', 55 | 'ToLogin': 'To login' 56 | } 57 | -------------------------------------------------------------------------------- /i18n/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import enUS from './en-US' 4 | import zhCN from './zh-CN' 5 | Vue.use(VueI18n) 6 | 7 | let lang = uni.getStorageSync('_lang').data 8 | if (!lang || lang === 'System') { 9 | const res = uni.getSystemInfoSync() 10 | lang = res.language 11 | uni.setStorageSync('_lang', 'System') 12 | } 13 | 14 | const i18n = new VueI18n({ 15 | locale: lang, 16 | fallbackLocale: 'en', 17 | silentFallbackWarn: true, 18 | messages: { 19 | 'en': enUS, 20 | 'zh-CN': zhCN 21 | } 22 | }) 23 | export default i18n 24 | -------------------------------------------------------------------------------- /i18n/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'ProjectApproval': '项目审批', 3 | 'ProjectAdjust': '项目调整', 4 | 'UserApproval': '用户审批', 5 | 'FilePreview': '文件预览', 6 | 'Project': '项目', 7 | 'YzCloud': '永中云服务', 8 | 'YzDocPreview': '永中云预览', 9 | 'YzDocEdit': '永中云编辑', 10 | 'YzDocPreviewEdit': '永中云预览/云编辑', 11 | 'DocUpload': '文档上传', 12 | 'ProjectDetail': '项目详细', 13 | 'Statistics': '统计', 14 | 'Profile': '我的', 15 | 'ToDo': '待办', 16 | 'Language': '语言', 17 | 'Empty': '空', 18 | 'HardLoading': '努力加载中', 19 | 'NoMore': '没有了', 20 | 'BasicInfo': '基本信息', 21 | 'UserName': '用户名', 22 | 'Name': '姓名', 23 | 'Role': '角色', 24 | 'RegisteredUser': '注册用户', 25 | 'ProjectThisMonth': '本月项目', 26 | 'Ok': '确认', 27 | 'ClearSearchHistory': '清空搜索历史', 28 | 'Settings': '软件设置', 29 | 'SignOut': '退出登录', 30 | 'ChangePassword': '修改密码', 31 | 'Upgrade': '软件升级', 32 | 'System': '跟随系统', 33 | 'English': '英文', 34 | 'Chinese': '中文', 35 | 'en': '英文', 36 | 'zh-CN': '中文', 37 | 'Theme': '主题', 38 | 'BluishGreen': '青碧', 39 | 'Blush': '酡颜', 40 | 'Volcano': '火山', 41 | 'Orange': '橘黄', 42 | 'IndigoBlue': '靛青', 43 | 'Indigo': '靛蓝', 44 | 'Verdant': '苍翠', 45 | 'Water': '水色', 46 | 'Grey': '灰色', 47 | 'PinkGold': '赤金', 48 | 'Dim': '黯', 49 | 'Verdigris': '铜绿', 50 | 'DeepBlack': '玄青', 51 | 'AgateGreen': '湖绿', 52 | 'DarkGreen': '苍色', 53 | 'DarkMode': '深色模式', 54 | 'Dark': '暗黑', 55 | 'ToLogin': '去登录' 56 | } 57 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | import store from './store' 4 | import i18n from './i18n' 5 | import _ from 'lodash' 6 | import MinRequest from './utils//MinRequest' 7 | import minApi from './api/api' 8 | import MinCache from './utils/MinCache' 9 | // mescroll-body 10 | import MescrollBody from "@/components/mescroll-uni/mescroll-body.vue" 11 | 12 | Vue.config.productionTip = false 13 | Vue.prototype.$store = store 14 | Vue.use(MinRequest) 15 | // 注册缓存器 16 | Vue.use(MinCache) 17 | // mescroll-body 18 | Vue.component('mescroll-body', MescrollBody) 19 | 20 | App.mpType = 'app' 21 | Vue.prototype._i18n = i18n 22 | Vue.prototype.$_ = _ 23 | 24 | const app = new Vue({ 25 | store, 26 | minApi, 27 | i18n, 28 | ...App 29 | }) 30 | app.$mount() 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uniapp-admin", 3 | "version": "2.1.0", 4 | "main": "index.js", 5 | "repository": "https://github.com/silianpan/uniapp-admin.git", 6 | "author": "silianpan ", 7 | "license": "MIT", 8 | "dependencies": { 9 | "crypto-js": "^4.0.0", 10 | "lodash": "^4.17.21", 11 | "vue-i18n": "^8.23.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "app-plus": { 7 | "bounce": "none" // 可选: 是否禁止iOS回弹和Android触顶触底的弧形阴影, 默认允许 (可配在 'globalStyle') 8 | }, 9 | "mp-alipay": { 10 | "allowsBounceVertical": "NO" // 可选: 取消支付宝和钉钉小程序的iOS回弹 (可配在 'globalStyle') 11 | } 12 | } 13 | }, 14 | { 15 | "path": "pages/login/login", 16 | "style": { 17 | "titleNView": false 18 | } 19 | }, 20 | { 21 | "path": "pages/index/project/detail-project" 22 | }, 23 | { 24 | "path": "pages/index/project/adjust-project" 25 | }, 26 | { 27 | "path": "pages/index/user/detail-user" 28 | }, 29 | { 30 | "path": "pages/file/file-preview" 31 | }, 32 | { 33 | "path": "pages/project/index" 34 | }, 35 | { 36 | "path": "pages/project/project-detail" 37 | }, 38 | { 39 | "path": "pages/analysis/index" 40 | }, 41 | { 42 | "path": "pages/user/index" 43 | }, 44 | { 45 | "path": "pages/user/setting" 46 | }, 47 | { 48 | "path": "pages/yzcloud/index" 49 | }, 50 | { 51 | "path": "pages/file/file-upload" 52 | }, 53 | { 54 | "path": "pages/yzcloud/yz-preview-callback" 55 | }, 56 | { 57 | "path": "pages/yzcloud/yz-edit" 58 | } 59 | ], 60 | "tabBar": { 61 | "color": "#8a8a8a", 62 | "selectedColor": "#4364F7", 63 | "backgroundColor": "#ffffff", 64 | "list": [{ 65 | "pagePath": "pages/index/index", 66 | "iconPath": "static/img/tabbar/todo.png", 67 | "selectedIconPath": "static/img/tabbar/todoHL.png" 68 | }, { 69 | "pagePath": "pages/yzcloud/index", 70 | "iconPath": "static/img/tabbar/yz.png", 71 | "selectedIconPath": "static/img/tabbar/yzHL.png" 72 | }, { 73 | "pagePath": "pages/project/index", 74 | "iconPath": "static/img/tabbar/project.png", 75 | "selectedIconPath": "static/img/tabbar/projectHL.png" 76 | }, { 77 | "pagePath": "pages/analysis/index", 78 | "iconPath": "static/img/tabbar/tongji.png", 79 | "selectedIconPath": "static/img/tabbar/tongjiHL.png" 80 | }, { 81 | "pagePath": "pages/user/index", 82 | "iconPath": "static/img/tabbar/user.png", 83 | "selectedIconPath": "static/img/tabbar/userHL.png" 84 | }] 85 | }, 86 | "globalStyle": { 87 | // "navigationBarTextStyle": "white", 88 | // "navigationBarTitleText": "uniapp-admin", 89 | // "navigationBarBackgroundColor": "#4364F7", 90 | // "backgroundColor": "#F8F8F8" 91 | // "mp-alipay": { 92 | // /* 支付宝小程序特有相关 */ 93 | // "transparentTitle": "always", 94 | // "allowsBounceVertical": "NO" 95 | // }, 96 | // "navigationBarBackgroundColor": "#0081ff", 97 | // "navigationBarTitleText": "uniapp-admin", 98 | // "navigationStyle": "custom", 99 | // "navigationBarTextStyle": "white" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /pages/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 64 | -------------------------------------------------------------------------------- /pages/analysis/time-table.vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 121 | 122 | 127 | -------------------------------------------------------------------------------- /pages/analysis/ucharts-demo.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 98 | 99 | 160 | -------------------------------------------------------------------------------- /pages/file/file-preview.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | -------------------------------------------------------------------------------- /pages/file/file-upload.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 61 | -------------------------------------------------------------------------------- /pages/index/audit-idea.vue: -------------------------------------------------------------------------------- 1 | 2 | 29 | 30 | 110 | -------------------------------------------------------------------------------- /pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 177 | 178 | 246 | -------------------------------------------------------------------------------- /pages/index/project/audit-project.vue: -------------------------------------------------------------------------------- 1 | 2 | 66 | 67 | 155 | 156 | 161 | -------------------------------------------------------------------------------- /pages/index/user/audit-user.vue: -------------------------------------------------------------------------------- 1 | 2 | 63 | 64 | 145 | -------------------------------------------------------------------------------- /pages/login/css/main.css: -------------------------------------------------------------------------------- 1 | .content { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content:center; 5 | /* margin-top: 128rpx; */ 6 | } 7 | 8 | /* 头部 logo */ 9 | .header { 10 | /* width:161rpx; */ 11 | /* height:161rpx; */ 12 | /* box-shadow:0rpx 0rpx 60rpx 0rpx rgba(0,0,0,0.1); */ 13 | /* border-radius:50%; */ 14 | /* background-color: #000000; */ 15 | margin-top: 128rpx; 16 | margin-bottom: 72rpx; 17 | margin-left: auto; 18 | margin-right: auto; 19 | } 20 | .header image{ 21 | width:180rpx; 22 | height:221rpx; 23 | /* border-radius:50%; */ 24 | } 25 | 26 | /* 主体 */ 27 | .main { 28 | display: flex; 29 | flex-direction: column; 30 | padding-left: 70rpx; 31 | padding-right: 70rpx; 32 | } 33 | .tips { 34 | color: #999999; 35 | font-size: 28rpx; 36 | margin-top: 64rpx; 37 | margin-left: 48rpx; 38 | } 39 | 40 | /* 其他登录方式 */ 41 | .other_login{ 42 | display: flex; 43 | flex-direction: row; 44 | justify-content: center; 45 | align-items: center; 46 | margin-top: 256rpx; 47 | text-align: center; 48 | } 49 | .login_icon{ 50 | border: none; 51 | font-size: 64rpx; 52 | margin: 0 64rpx 0 64rpx; 53 | color: rgba(0,0,0,0.7) 54 | } 55 | .wechat_color{ 56 | color: #83DC42; 57 | } 58 | .weibo_color{ 59 | color: #F9221D; 60 | } 61 | .github_color{ 62 | color: #24292E; 63 | } 64 | 65 | /* 底部 */ 66 | .footer{ 67 | display: flex; 68 | flex-direction: row; 69 | justify-content: center; 70 | align-items: center; 71 | font-size: 28rpx; 72 | margin-top: 64rpx; 73 | color: rgba(0,0,0,0.7); 74 | text-align: center; 75 | height: 40rpx; 76 | line-height: 40rpx; 77 | } 78 | .footer text{ 79 | font-size: 24rpx; 80 | margin-left: 15rpx; 81 | margin-right: 15rpx; 82 | } -------------------------------------------------------------------------------- /pages/login/forget.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 108 | 109 | 113 | -------------------------------------------------------------------------------- /pages/login/login.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 96 | 97 | 122 | -------------------------------------------------------------------------------- /pages/project/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 80 | -------------------------------------------------------------------------------- /pages/project/project-list.vue: -------------------------------------------------------------------------------- 1 | 2 | 54 | 55 | 126 | 127 | 132 | -------------------------------------------------------------------------------- /pages/user/index.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 211 | -------------------------------------------------------------------------------- /pages/user/setting.vue: -------------------------------------------------------------------------------- 1 | 2 | 64 | 65 | 181 | -------------------------------------------------------------------------------- /pages/yzcloud/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 85 | -------------------------------------------------------------------------------- /pages/yzcloud/signclient.js: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js' 2 | 3 | function uniqSortParams(params) { 4 | delete params.sign 5 | 6 | var var5 = [] 7 | var var6 = 0 8 | for (var key in params) { 9 | var5[var6] = key 10 | var6++ 11 | } 12 | var5.sort(function(a, b) { 13 | return a.localeCompare(b, 'zh-CN') 14 | }) 15 | 16 | var result = "" 17 | for (var var7 = 0; var7 < var5.length; var7++) { 18 | var key = var5[var7] 19 | var var8 = params[key] 20 | var8.sort(function(a, b) { 21 | return a.localeCompare(b, 'zh-CN') 22 | }) 23 | 24 | if (var8 != null && var8.length > 0) { 25 | for (var var9 = 0; var9 < var8.length; var9++) { 26 | result += key + "=" + var8[var9] 27 | } 28 | } else { 29 | result += key + "=" 30 | } 31 | } 32 | return result 33 | } 34 | 35 | function hmacSHA256(data, key) { 36 | data != null ? data : "" 37 | var var2 = CryptoJS.HmacSHA256(data, key) 38 | var var3 = var2.toString(CryptoJS.enc.Hex) 39 | return var3.toUpperCase() 40 | } 41 | 42 | export const generateSign = (secret, params) => { 43 | var fullParamStr = uniqSortParams(params) 44 | return hmacSHA256(fullParamStr, secret) 45 | } 46 | -------------------------------------------------------------------------------- /pages/yzcloud/yz-edit.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 46 | -------------------------------------------------------------------------------- /pages/yzcloud/yz-preview-callback.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 88 | -------------------------------------------------------------------------------- /static/img/backtop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/backtop.png -------------------------------------------------------------------------------- /static/img/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/edit.png -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/logo.png -------------------------------------------------------------------------------- /static/img/project/asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/project/asset.png -------------------------------------------------------------------------------- /static/img/project/build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/project/build.png -------------------------------------------------------------------------------- /static/img/project/land.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/project/land.png -------------------------------------------------------------------------------- /static/img/project/purchase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/project/purchase.png -------------------------------------------------------------------------------- /static/img/tabbar/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/home.png -------------------------------------------------------------------------------- /static/img/tabbar/homeHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/homeHL.png -------------------------------------------------------------------------------- /static/img/tabbar/org.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/org.png -------------------------------------------------------------------------------- /static/img/tabbar/orgHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/orgHL.png -------------------------------------------------------------------------------- /static/img/tabbar/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/project.png -------------------------------------------------------------------------------- /static/img/tabbar/projectHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/projectHL.png -------------------------------------------------------------------------------- /static/img/tabbar/todo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/todo.png -------------------------------------------------------------------------------- /static/img/tabbar/todoHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/todoHL.png -------------------------------------------------------------------------------- /static/img/tabbar/tongji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/tongji.png -------------------------------------------------------------------------------- /static/img/tabbar/tongjiHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/tongjiHL.png -------------------------------------------------------------------------------- /static/img/tabbar/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/user.png -------------------------------------------------------------------------------- /static/img/tabbar/userHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/userHL.png -------------------------------------------------------------------------------- /static/img/tabbar/yz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/yz.png -------------------------------------------------------------------------------- /static/img/tabbar/yz2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/yz2.png -------------------------------------------------------------------------------- /static/img/tabbar/yzHL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/yzHL.png -------------------------------------------------------------------------------- /static/img/tabbar/yzHL2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/tabbar/yzHL2.png -------------------------------------------------------------------------------- /static/img/user/agencyOrg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/img/user/agencyOrg.png -------------------------------------------------------------------------------- /static/uni.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-admin/9c866f28530db2f7d9ea71cc06864da46c8d83da/static/uni.ttf -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import modules from './modules/index.js' 4 | Vue.use(Vuex) 5 | 6 | const store = new Vuex.Store(modules) 7 | export default store 8 | -------------------------------------------------------------------------------- /store/modules/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import _ from 'lodash' 3 | export default { 4 | state: { 5 | themeBgColor: '', 6 | darkMode: null 7 | }, 8 | getters: { 9 | themeBgColor: state => { 10 | const color = Vue.prototype.$cache.get('_themeBgColor') 11 | if (_.isEmpty(color) && _.isEmpty(state.themeBgColor)) { 12 | return '#27547d' 13 | } 14 | return !_.isEmpty(state.themeBgColor) ? state.themeBgColor : color 15 | }, 16 | darkMode: state => { 17 | const dark = Vue.prototype.$cache.get('_darkMode') 18 | if (_.isNil(dark) && _.isNil(state.darkMode)) { 19 | return false 20 | } 21 | return !_.isNil(state.darkMode) ? state.darkMode : dark 22 | } 23 | }, 24 | mutations: { 25 | setThemeBgColor: (state, themeBgColor) => { 26 | // state.themeBgColor = themeBgColor 27 | Vue.set(state, 'themeBgColor', themeBgColor) 28 | Vue.prototype.$cache.set('_themeBgColor', themeBgColor, 0) 29 | }, 30 | setDarkMode: (state, darkMode) => { 31 | Vue.set(state, 'darkMode', darkMode) 32 | Vue.prototype.$cache.set('_darkMode', darkMode, 0) 33 | } 34 | }, 35 | actions: { 36 | initThemeBgColor({ commit }, themeBgColor) { 37 | commit('setThemeBgColor', themeBgColor) 38 | }, 39 | initDarkMode({ commit }, darkMode) { 40 | commit('setDarkMode', darkMode) 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /store/modules/index.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | const files = require.context('.', false, /\.js$/) 4 | const modules = {} 5 | 6 | files.keys().forEach(key => { 7 | if (key === './index.js') return 8 | _.mergeWith(modules,files(key).default) 9 | }) 10 | export default modules 11 | -------------------------------------------------------------------------------- /store/modules/user.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import _ from 'lodash' 3 | export default { 4 | state: { 5 | user: null 6 | }, 7 | mutations: { 8 | login(state, user) { 9 | state.user = user 10 | // 缓存用户信息 11 | Vue.prototype.$cache.set('_userInfo', user, 0) 12 | }, 13 | logout(state) { 14 | state.user = null 15 | // 清理缓存用户信息 16 | Vue.prototype.$cache.delete('_userInfo') 17 | } 18 | }, 19 | actions: { 20 | autoLogin({ commit, getters, dispatch }) { 21 | // 判断本地是否有账号信息,如果有,就自动重新登录 22 | if (getters.user && getters.user.id && getters.user.name && getters.user.passwd) { 23 | const params = { 24 | name: getters.user.name, 25 | passwd: getters.user.passwd 26 | } 27 | uni.showLoading({ 28 | title: '自动登录中...' 29 | }) 30 | dispatch('login', params).then(res => { 31 | uni.hideLoading() 32 | // uni.showToast({ 33 | // title: '已自动登录!', 34 | // icon: 'success' 35 | // }) 36 | }).catch(() => { 37 | uni.hideLoading() 38 | uni.showToast({ 39 | title: '会话过期,请重新登录!', 40 | icon: 'none' 41 | }) 42 | setTimeout(() => { 43 | uni.reLaunch({ 44 | url: '/pages/login/login' 45 | }) 46 | }, 1000) 47 | }) 48 | } else { 49 | // 如果本地没有账号信息,就提示用户必须登录 50 | uni.showModal({ 51 | title: '未登录', 52 | content: '您未登录,需要登录后才能继续', 53 | showCancel: false, 54 | confirmText: '确定', 55 | success: res => { 56 | if (res.confirm) { 57 | uni.reLaunch({ 58 | url: '/pages/login/login' 59 | }) 60 | } 61 | } 62 | }) 63 | } 64 | }, 65 | login({ commit }, params) { 66 | return new Promise((resolve, reject) => { 67 | Vue.prototype.$minApi.login(params).then(res => { 68 | if (res.ok()) { 69 | let tmp = { ...params, ...res.data } 70 | commit('login', tmp) 71 | 72 | // 关于消息推送的保存 73 | // 保存clientid到服务器 74 | // #ifdef APP-PLUS 75 | const clientInfo = plus.push.getClientInfo() 76 | let pushUser = { 77 | clientid: clientInfo.clientid, 78 | appid: clientInfo.appid, 79 | appkey: clientInfo.appkey, 80 | userName: tmp.userName, 81 | userRole: tmp.roleStr 82 | } 83 | // 提交api请求,服务端保存客户端角色信息 84 | // Vue.prototype.$minApi.savePushUser(pushUser) 85 | // #endif 86 | 87 | resolve(res) 88 | } else { 89 | reject(res) 90 | } 91 | }).catch(err => { 92 | reject(err) 93 | }) 94 | }) 95 | }, 96 | logout({ commit }) { 97 | commit('logout') 98 | uni.reLaunch({ 99 | url: '/pages/login/login' 100 | }) 101 | } 102 | }, 103 | getters: { 104 | user: state => { 105 | if (state.user) { 106 | return state.user 107 | } 108 | return Vue.prototype.$cache.get('_userInfo') 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24upx; 43 | $uni-font-size-base:28upx; 44 | $uni-font-size-lg:32upx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40upx; 48 | $uni-img-size-base:52upx; 49 | $uni-img-size-lg:80upx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4upx; 53 | $uni-border-radius-base: 6upx; 54 | $uni-border-radius-lg: 12upx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20upx; 60 | $uni-spacing-row-lg: 30upx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8upx; 64 | $uni-spacing-col-base: 16upx; 65 | $uni-spacing-col-lg: 24upx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40upx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36upx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30upx; -------------------------------------------------------------------------------- /unipush离线集成指南.md: -------------------------------------------------------------------------------- 1 | ## 关于unipush离线集成的方法 2 | ### 准备 3 | 1. 通过官方途径申请UniPush账号,参考[使用指南](http://ask.dcloud.net.cn/article/35622) 4 | 5 | 2. [离线SDK](https://ask.dcloud.net.cn/article/103) 6 | 7 | ### 配置 8 | 9 | * 依赖文件 10 | 11 | 将aps-unipush-release.aar拷贝到已有项目libs文件夹下。 12 | 13 | * gradle配置 14 | 15 | 打开build.gradle,在defaultConfig添加manifestPlaceholders节点,如下图所示,将io.dcloud.HBuilder替换成自己的应用包名,将appid等信息替换成申请之后的appid等。 16 | ~~~ 17 | android { 18 | defaultConfig { 19 | manifestPlaceholders = [ 20 | "plus.unipush.appid" : "pPyZWvH3Fa6PXba19ID0091", 21 | "plus.unipush.appkey" : "b7dOGlNPHR7pqwUxcXPVi44", 22 | "plus.unipush.appsecret": "IxVYAT9qws8dlNElacmSg12", 23 | "apk.applicationId":"io.dcloud.HBuilder" 24 | ] 25 | } 26 | } 27 | ~~~ 28 | 29 | * 厂商配置 30 | 31 | 添加下列内容到androidmanifest.xml中(未申请平台无需添加) 32 | ~~~ 33 | 36 | 39 | 42 | 45 | 48 | 51 | 54 | 57 | 60 | ~~~ 61 | 62 | 修改build.gradle,添加对应平台申请的appkey或appid(键名必须统一,如XIAOMI_APP_ID比如同时存在于build.gradle文件和Androidmanifest.xml文件中),如下所示: 63 | 64 | ~~~ 65 | android { 66 | defaultConfig { 67 | manifestPlaceholders = [ 68 | "plus.unipush.appid" : "pPyZWvH3Fa6PXba19ID0091", 69 | "plus.unipush.appkey" : "b7dOGlNPHR7pqwUxcXPVi45", 70 | "plus.unipush.appsecret": "IxVYAT9qws8dlNElacmSg12", 71 | "apk.applicationId":"io.dcloud.HBuilder", 72 | "XIAOMI_APP_ID":"ccccccccc" 73 | ] 74 | } 75 | } 76 | ~~~ 77 | 78 | * dcloud_properties.xml配置 79 | 80 | 在properties中添加如下配置,features节点与services节点必须同时配置! 81 | 82 | ~~~ 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ~~~ 94 | 95 | * 其余配置 96 | 97 | oppo集成UniPush时需在Androidmanifest.xml的入口activity中添加如下配置: 98 | 99 | ~~~ 100 | 102 | 103 | 104 | 105 | 106 | /*oppo配置开始*/ 107 | 108 | 109 | 110 | 111 | /*oppo配置结束*/ 112 | 113 | ~~~ 114 | 115 | ### 注意 116 | 117 | * UniPush与个推及小米推送存在冲突,使用时请确保小米推送及个推相关文件已删除。 118 | * 上述账号信息仅为展示使用,使用时需替换成自己申请的appkey等信息。 119 | * 为了最大化的减少配置,权限及其他厂商配置统统放入aar中。 -------------------------------------------------------------------------------- /utils/MinCache.js: -------------------------------------------------------------------------------- 1 | let cacheMap = new Map() 2 | let timeoutDefault = 1200 3 | 4 | function isTimeout(name) { 5 | const data = cacheMap.get(name) 6 | if (!data) return true 7 | if (data.timeout === 0) return false 8 | const currentTime = Date.now() 9 | const overTime = (currentTime - data.createTime) / 1000 10 | if (overTime > data.timeout) { 11 | cacheMap.delete(name) 12 | if (name.startsWith('_')) { 13 | try { 14 | uni.removeStorageSync(name) 15 | } catch (e) { 16 | console.log(e) 17 | } 18 | } 19 | return true 20 | } 21 | return false 22 | } 23 | 24 | class CacheCell { 25 | constructor(data, timeout) { 26 | this.data = data 27 | this.timeout = timeout 28 | this.createTime = Date.now() 29 | } 30 | } 31 | 32 | class MinCache { 33 | constructor(timeout) { 34 | try { 35 | const res = uni.getStorageInfoSync() 36 | res.keys.forEach(name => { 37 | try { 38 | const value = uni.getStorageSync(name) 39 | cacheMap.set(name, value) 40 | } catch (e) { 41 | console.log(e) 42 | } 43 | }) 44 | } catch (e) { 45 | console.log(e) 46 | } 47 | timeoutDefault = timeout 48 | } 49 | set(name, data, timeout = timeoutDefault) { 50 | const cachecell = new CacheCell(data, timeout) 51 | let cache = null 52 | if (name.startsWith('_')) { 53 | try { 54 | uni.setStorageSync(name, cachecell) 55 | cache = cacheMap.set(name, cachecell) 56 | } catch (e) { 57 | console.log(e) 58 | } 59 | } else { 60 | cache = cacheMap.set(name, cachecell) 61 | } 62 | return cache 63 | } 64 | get(name) { 65 | return isTimeout(name) ? null : cacheMap.get(name).data 66 | } 67 | delete(name) { 68 | let value = false 69 | if (name.startsWith('_')) { 70 | try { 71 | uni.removeStorageSync(name) 72 | value = cacheMap.delete(name) 73 | } catch (e) { 74 | console.log(e) 75 | } 76 | } else { 77 | value = cacheMap.delete(name) 78 | } 79 | return value 80 | } 81 | has(name) { 82 | return !isTimeout(name) 83 | } 84 | clear() { 85 | let value = false 86 | try { 87 | uni.clearStorageSync() 88 | cacheMap.clear() 89 | value = true 90 | } catch (e) { 91 | console.log(e) 92 | } 93 | return value 94 | } 95 | } 96 | 97 | MinCache.install = function(Vue, { 98 | timeout = 1200 99 | } = {}) { 100 | Vue.prototype.$cache = new MinCache(timeout) 101 | } 102 | 103 | export default MinCache 104 | -------------------------------------------------------------------------------- /utils/MinRequest.js: -------------------------------------------------------------------------------- 1 | const config = Symbol('config') 2 | const isCompleteURL = Symbol('isCompleteURL') 3 | const requestBefore = Symbol('requestBefore') 4 | const requestAfter = Symbol('requestAfter') 5 | import { checkLogin, checkResult } from '@/utils/checkResponse' 6 | 7 | class MinRequest { 8 | [config] = { 9 | baseURL: '', 10 | header: { 11 | 'content-type': 'application/json' 12 | }, 13 | method: 'GET', 14 | dataType: 'json', 15 | responseType: 'text' 16 | } 17 | 18 | interceptors = { 19 | request: (func) => { 20 | if (func) { 21 | MinRequest[requestBefore] = func 22 | } else { 23 | MinRequest[requestBefore] = (request) => request 24 | } 25 | 26 | }, 27 | response: (func) => { 28 | if (func) { 29 | MinRequest[requestAfter] = func 30 | } else { 31 | MinRequest[requestAfter] = (response) => response 32 | } 33 | } 34 | } 35 | 36 | static[requestBefore](config) { 37 | return config 38 | } 39 | 40 | static[requestAfter](response) { 41 | return response 42 | } 43 | 44 | static[isCompleteURL](url) { 45 | return /(http|https):\/\/([\w.]+\/?)\S*/.test(url) 46 | } 47 | 48 | setConfig(func) { 49 | this[config] = func(this[config]) 50 | } 51 | 52 | request(options = {}) { 53 | options.baseURL = options.baseURL || this[config].baseURL 54 | options.dataType = options.dataType || this[config].dataType 55 | options.url = MinRequest[isCompleteURL](options.url) ? options.url : (options.baseURL + options.url) 56 | options.data = options.data 57 | options.header = { ...options.header, 58 | ...this[config].header 59 | } 60 | options.method = options.method || this[config].method 61 | 62 | options = { ...options, 63 | ...MinRequest[requestBefore](options) 64 | } 65 | 66 | return new Promise((resolve, reject) => { 67 | options.success = function(res) { 68 | resolve(MinRequest[requestAfter](res)) 69 | } 70 | options.fail = function(err) { 71 | reject(MinRequest[requestAfter](err)) 72 | } 73 | uni.request(options) 74 | }) 75 | } 76 | 77 | get(url, data, options = {}) { 78 | options.url = url 79 | options.data = data 80 | options.method = 'GET' 81 | return this.request(options).then(checkLogin).then(checkResult) 82 | } 83 | 84 | post(url, data, options = {}) { 85 | options.url = url 86 | options.data = data 87 | options.method = 'POST' 88 | return this.request(options).then(checkLogin).then(checkResult) 89 | } 90 | 91 | delete(url, data, options = {}) { 92 | options.url = url 93 | options.data = data 94 | options.method = 'DELETE' 95 | return this.request(options).then(checkLogin).then(checkResult) 96 | } 97 | } 98 | 99 | MinRequest.install = function(Vue) { 100 | Vue.mixin({ 101 | beforeCreate: function() { 102 | if (this.$options.minApi) { 103 | Vue._minApi = this.$options.minApi 104 | } 105 | } 106 | }) 107 | Object.defineProperty(Vue.prototype, '$minApi', { 108 | get: function() { 109 | return Vue._minApi.apis 110 | } 111 | }) 112 | } 113 | 114 | export default MinRequest 115 | -------------------------------------------------------------------------------- /utils/MinRouter.js: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString 2 | 3 | function isObject(value) { 4 | return toString.call(value) === '[object Object]' 5 | } 6 | 7 | function isString(value) { 8 | return toString.call(value) === '[object String]' 9 | } 10 | 11 | function isDefault(value) { 12 | return value === void 0 13 | } 14 | 15 | function openPage(args) { 16 | let name, query = {}, 17 | queryStr = null, 18 | path, type, isName = false 19 | switch (true) { 20 | case isObject(args): 21 | ({ 22 | name, 23 | query = {} 24 | } = args) 25 | break 26 | case isString(args): 27 | name = args 28 | break 29 | default: 30 | throw new Error('参数必须是对象或者字符串') 31 | } 32 | if (isObject(query)) { 33 | queryStr = encodeURIComponent(JSON.stringify(query)) 34 | } else { 35 | throw new Error('query数据必须是Object') 36 | } 37 | this.$minRouter.forEach(item => { 38 | if (item.name === name) { 39 | path = item.path 40 | type = item.type || 'navigateTo' 41 | isName = true 42 | } 43 | }) 44 | if (!isName) { 45 | throw new Error(`没有${name}页面`) 46 | } 47 | if (!['navigateTo', 'switchTab', 'reLaunch', 'redirectTo'].includes(type)) { 48 | throw new Error(`name:${name}里面的type必须是以下的值['navigateTo', 'switchTab', 'reLaunch', 'redirectTo']`) 49 | } 50 | return new Promise((resolve, reject) => { 51 | uni[type]({ 52 | url: `/${path}?query=${queryStr}`, 53 | success: resolve, 54 | fail: reject 55 | }) 56 | }) 57 | } 58 | 59 | function parseURL() { 60 | const query = this.$root.$mp.query.query 61 | if (query) { 62 | return JSON.parse(decodeURIComponent(query)) 63 | } else { 64 | return {} 65 | } 66 | } 67 | 68 | function install(Vue) { 69 | Vue.mixin({ 70 | beforeCreate: function() { 71 | if (!isDefault(this.$options.minRouter)) { 72 | Vue._minRouter = this.$options.minRouter 73 | } 74 | } 75 | }) 76 | Object.defineProperty(Vue.prototype, '$minRouter', { 77 | get: function() { 78 | return Vue._minRouter._router 79 | } 80 | }) 81 | Object.defineProperty(Vue.prototype, '$parseURL', { 82 | get: function() { 83 | return Vue._minRouter.parseURL 84 | } 85 | }) 86 | Object.defineProperty(Vue.prototype, '$openPage', { 87 | get: function() { 88 | return Vue._minRouter.openPage 89 | } 90 | }) 91 | } 92 | 93 | function MinRouter(options) { 94 | if (!(this instanceof MinRouter)) { 95 | throw Error("MinRouter是一个构造函数,应该用`new`关键字调用") 96 | } 97 | isDefault(options) && (options = {}) 98 | this.options = options 99 | this._router = options.routes || [] 100 | } 101 | MinRouter.install = install 102 | MinRouter.prototype.openPage = openPage 103 | MinRouter.prototype.parseURL = parseURL 104 | export default MinRouter 105 | -------------------------------------------------------------------------------- /utils/checkResponse.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | const HttpErrorCode = { 3 | 400: '请求错误', 4 | 401: '未授权,请登陆', 5 | 403: '拒绝访问', 6 | 404: '请求地址出错', 7 | 408: '请求超时', 8 | 500: '服务器内部错误', 9 | 501: '服务未实现', 10 | 502: '网关错误', 11 | 503: '服务不可用', 12 | 504: '网关超时', 13 | 505: 'HTTP版本不受支持' 14 | } 15 | 16 | // 去抖 17 | function debounce(fn, wait) { 18 | let callback = fn 19 | let timerId = null 20 | 21 | function debounced() { 22 | // 保存作用域 23 | let context = this 24 | // 保存参数,例如 event 对象 25 | let args = arguments 26 | 27 | clearTimeout(timerId) 28 | timerId = setTimeout(function() { 29 | callback.apply(context, args) 30 | }, wait) 31 | } 32 | 33 | // 返回一个闭包 34 | return debounced 35 | } 36 | 37 | // 节流 38 | function throttle(fn, wait) { 39 | let callback = fn 40 | let timerId = null 41 | 42 | // 是否是第一次执行 43 | let firstInvoke = true 44 | 45 | function throttled() { 46 | let context = this 47 | let args = arguments 48 | 49 | // 如果是第一次触发,直接执行 50 | if (firstInvoke) { 51 | callback.apply(context, args) 52 | firstInvoke = false 53 | return 54 | } 55 | 56 | // 如果定时器已存在,直接返回。 57 | if (timerId) { 58 | return 59 | } 60 | 61 | timerId = setTimeout(function() { 62 | // 注意这里 将 clearTimeout 放到 内部来执行了 63 | clearTimeout(timerId) 64 | timerId = null 65 | 66 | callback.apply(context, args) 67 | }, wait) 68 | } 69 | 70 | // 返回一个闭包 71 | return throttled 72 | } 73 | 74 | /** 75 | * 跳转到登录页面 76 | */ 77 | const goLoginDebounce = debounce( 78 | function() { 79 | Vue.prototype.$store.dispatch('logout') 80 | }, 81 | 250 82 | ) 83 | /** 84 | * 展示出错误信息 85 | */ 86 | const showErrorMsg = debounce( 87 | function(msg) { 88 | uni.showToast({ 89 | title: msg, 90 | icon: 'none' 91 | }) 92 | }, 93 | 250 94 | ) 95 | 96 | /** 97 | * 98 | *检查reponse返回状态 99 | * @author silianpan 100 | * @date 2019-11-29 101 | * @param {*} [response={}] 102 | * @returns 103 | */ 104 | export const checkStatus = (response = {}) => { 105 | let { 106 | status, 107 | statusText 108 | } = response 109 | if (status === 200 || status === 304) { 110 | return response.data 111 | } else { 112 | let msg = HttpErrorCode[status] || statusText 113 | showErrorMsg(msg) 114 | return { 115 | code: status, 116 | msg: msg 117 | } 118 | } 119 | } 120 | 121 | /** 122 | *检查登录状态 123 | * 124 | * @author silianpan 125 | * @date 2019-11-29 126 | * @param {*} response 127 | * @returns 128 | */ 129 | export const checkLogin = (response) => { 130 | let { code } = response 131 | if (code === 401) { 132 | goLoginDebounce() 133 | } 134 | return response 135 | } 136 | /** 137 | *检查服务器返回结果 138 | * @author silianpan 139 | * @date 2019-11-29 140 | * @param {*} result 141 | * @returns 142 | */ 143 | export const checkResult = (result) => { 144 | // 对后台返回结果检测 145 | // if (!result.isOk) { 146 | // showErrorMsg(result.errMsg || '请求失败') 147 | // } 148 | let { code } = result 149 | result.ok = function() { 150 | return false 151 | } 152 | if (code === 200) { 153 | result.ok = function() { 154 | return true 155 | } 156 | } else { 157 | showErrorMsg(result.msg || HttpErrorCode[code] || '请求失败') 158 | } 159 | return result 160 | } 161 | -------------------------------------------------------------------------------- /utils/datetime.js: -------------------------------------------------------------------------------- 1 | export const formatDateDay = time => { 2 | return parseTime(time, '{y}-{m}-{d}') 3 | } 4 | export const formatDate = time => { 5 | return parseTime(time, '{y}-{m}-{d} {h}:{i}') 6 | } 7 | const parseTime = (time, cFormat) => { 8 | if (!time) { 9 | return null 10 | } 11 | if (arguments.length === 0) { 12 | return null 13 | } 14 | const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}' 15 | let date 16 | if (typeof time === 'object') { 17 | date = time 18 | } else { 19 | if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { 20 | time = parseInt(time) 21 | } 22 | if ((typeof time === 'number') && (time.toString().length === 10)) { 23 | time = time * 1000 24 | } 25 | date = new Date(time) 26 | } 27 | const formatObj = { 28 | y: date.getFullYear(), 29 | m: date.getMonth() + 1, 30 | d: date.getDate(), 31 | h: date.getHours(), 32 | i: date.getMinutes(), 33 | s: date.getSeconds(), 34 | a: date.getDay() 35 | } 36 | const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { 37 | let value = formatObj[key] 38 | // Note: getDay() returns 0 on Sunday 39 | if (key === 'a') { 40 | return ['日', '一', '二', '三', '四', '五', '六'][value] 41 | } 42 | if (result.length > 0 && value < 10) { 43 | value = '0' + value 44 | } 45 | return value || 0 46 | }) 47 | return time_str 48 | } 49 | export const formatSimpleTime = (time, option) => { 50 | if (('' + time).length === 10) { 51 | time = parseInt(time) * 1000 52 | } else { 53 | time = +time 54 | } 55 | const d = new Date(time) 56 | const now = Date.now() 57 | 58 | const diff = (now - d) / 1000 59 | 60 | if (diff < 30) { 61 | return '刚刚' 62 | } else if (diff < 3600) { 63 | // less 1 hour 64 | return Math.ceil(diff / 60) + '分钟前' 65 | } else if (diff < 3600 * 24) { 66 | return Math.ceil(diff / 3600) + '小时前' 67 | } else if (diff < 3600 * 24 * 2) { 68 | return '1天前' 69 | } 70 | if (option) { 71 | return parseTime(time, option) 72 | } else { 73 | return ( 74 | d.getMonth() + 75 | 1 + 76 | '月' + 77 | d.getDate() + 78 | '日' + 79 | d.getHours() + 80 | '时' + 81 | d.getMinutes() + 82 | '分' 83 | ) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /utils/graceChecker.js: -------------------------------------------------------------------------------- 1 | /** 2 | 数据验证(表单验证) 3 | 来自 grace.hcoder.net 4 | 作者 hcoder 深海 5 | */ 6 | module.exports = { 7 | error:'', 8 | check : function (data, rule){ 9 | for(var i = 0; i < rule.length; i++){ 10 | if (!rule[i].checkType){return true;} 11 | if (!rule[i].name) {return true;} 12 | if (!rule[i].errorMsg) {return true;} 13 | if (!data[rule[i].name]) {this.error = rule[i].errorMsg; return false;} 14 | switch (rule[i].checkType){ 15 | case 'string': 16 | var reg = new RegExp('^.{' + rule[i].checkRule + '}$'); 17 | if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;} 18 | break; 19 | case 'int': 20 | var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$'); 21 | if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;} 22 | break; 23 | break; 24 | case 'between': 25 | if (!this.isNumber(data[rule[i].name])){ 26 | this.error = rule[i].errorMsg; 27 | return false; 28 | } 29 | var minMax = rule[i].checkRule.split(','); 30 | minMax[0] = Number(minMax[0]); 31 | minMax[1] = Number(minMax[1]); 32 | if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) { 33 | this.error = rule[i].errorMsg; 34 | return false; 35 | } 36 | break; 37 | case 'betweenD': 38 | var reg = /^-?[1-9][0-9]?$/; 39 | if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; } 40 | var minMax = rule[i].checkRule.split(','); 41 | minMax[0] = Number(minMax[0]); 42 | minMax[1] = Number(minMax[1]); 43 | if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) { 44 | this.error = rule[i].errorMsg; 45 | return false; 46 | } 47 | break; 48 | case 'betweenF': 49 | var reg = /^-?[0-9][0-9]?.+[0-9]+$/; 50 | if (!reg.test(data[rule[i].name])){this.error = rule[i].errorMsg; return false;} 51 | var minMax = rule[i].checkRule.split(','); 52 | minMax[0] = Number(minMax[0]); 53 | minMax[1] = Number(minMax[1]); 54 | if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) { 55 | this.error = rule[i].errorMsg; 56 | return false; 57 | } 58 | break; 59 | case 'same': 60 | if (data[rule[i].name] != rule[i].checkRule) { this.error = rule[i].errorMsg; return false;} 61 | break; 62 | case 'notsame': 63 | if (data[rule[i].name] == rule[i].checkRule) { this.error = rule[i].errorMsg; return false; } 64 | break; 65 | case 'email': 66 | var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/; 67 | if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; } 68 | break; 69 | case 'phoneno': 70 | var reg = /^1[0-9]{10,10}$/; 71 | if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; } 72 | break; 73 | case 'zipcode': 74 | var reg = /^[0-9]{6}$/; 75 | if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; } 76 | break; 77 | case 'reg': 78 | var reg = new RegExp(rule[i].checkRule); 79 | if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; } 80 | break; 81 | case 'in': 82 | if(rule[i].checkRule.indexOf(data[rule[i].name]) == -1){ 83 | this.error = rule[i].errorMsg; return false; 84 | } 85 | break; 86 | case 'notnull': 87 | if(data[rule[i].name] == null || data[rule[i].name].length < 1){this.error = rule[i].errorMsg; return false;} 88 | break; 89 | } 90 | } 91 | return true; 92 | }, 93 | isNumber : function (checkVal){ 94 | var reg = /^-?[1-9][0-9]?.?[0-9]*$/; 95 | return reg.test(checkVal); 96 | } 97 | } -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | import globalConfig from '@/config' 2 | 3 | /** 4 | * 随机从数据中选取n项元素 5 | */ 6 | export const getRandomArrayElements = (arr, count) => { 7 | var shuffled = arr.slice(0), 8 | i = arr.length, 9 | min = i - count, 10 | temp, index 11 | while (i-- > min) { 12 | index = Math.floor((i + 1) * Math.random()) 13 | temp = shuffled[index] 14 | shuffled[index] = shuffled[i] 15 | shuffled[i] = temp 16 | } 17 | return shuffled.slice(min) 18 | } 19 | 20 | export const formatArr = (segmentType, sep = '/') => { 21 | let result = '' 22 | if (segmentType) { 23 | try { 24 | const segmentArr = JSON.parse(segmentType) 25 | segmentArr.forEach(item => { 26 | result += item + sep 27 | }) 28 | } catch (error) { 29 | return segmentType 30 | } 31 | } 32 | if (result) { 33 | let len = sep.length > 1 ? 2 : 1 34 | result = result.substring(0, result.length - len) 35 | } 36 | return result 37 | } 38 | export const formatProjectType = pt => { 39 | if (!pt) { 40 | return '' 41 | } 42 | if (pt.indexOf('工程施工') !== -1) { 43 | return 'build.png' 44 | } else if (pt.indexOf('采购') !== -1) { 45 | return 'purchase.png' 46 | } else if (pt.indexOf('软件研发') !== -1) { 47 | return 'asset.png' 48 | } else if (pt.indexOf('系统集成') !== -1) { 49 | return 'land.png' 50 | } 51 | return '' 52 | } 53 | // 格式化附件json字符串 54 | export const formatJsonStr = attaJsonStr => { 55 | if (attaJsonStr && attaJsonStr.length > 0) { 56 | try { 57 | return JSON.parse(attaJsonStr) 58 | } catch (e) { 59 | return [] 60 | } 61 | } 62 | return [] 63 | } 64 | 65 | export const formatAuditStatus = status => { 66 | let text = '' 67 | let color = '' 68 | switch (status) { 69 | case '-1': 70 | ; 71 | (() => { 72 | text = '未提交' 73 | color = 'primary' 74 | })() 75 | break 76 | case '0': 77 | ; 78 | (() => { 79 | text = '不通过' 80 | color = 'error' 81 | })() 82 | break 83 | case '1': 84 | ; 85 | (() => { 86 | text = '通过' 87 | color = 'success' 88 | })() 89 | break 90 | default: 91 | ; 92 | (() => { 93 | text = '待审核' 94 | color = 'warning' 95 | })() 96 | break 97 | } 98 | return { 99 | color, 100 | text 101 | } 102 | } 103 | 104 | /** 105 | * 附件预览 106 | * @param {Object} atta附件{name:'',url:''} 107 | */ 108 | export const filePreview = atta => { 109 | let name = atta.name 110 | let url = atta.url 111 | // 判断文件类型 112 | if (url && typeof url === 'string') { 113 | let index = url.lastIndexOf('.') 114 | let suffix = url.substring(index + 1) 115 | if (suffix) { 116 | suffix = suffix.toLowerCase() 117 | switch (suffix) { 118 | case 'pdf': 119 | case 'doc': 120 | case 'docx': 121 | case 'xls': 122 | case 'xlsx': 123 | case 'ppt': 124 | case 'pptx': 125 | // 转换为pdf进行预览 126 | uni.navigateTo({ 127 | url: '/pages/file/file-preview?name=' + name + '&url=' + url 128 | }) 129 | break 130 | case 'jpg': 131 | case 'jpeg': 132 | case 'png': 133 | case 'bmp': 134 | case 'gif': 135 | // 预览图片 136 | uni.previewImage({ 137 | urls: [globalConfig.fileAddr + url], 138 | longPressActions: { 139 | itemList: ['发送给朋友', '保存图片', '收藏'], 140 | success: function(data) { 141 | console.log('选中了第' + (data.tapIndex + 1) + '个按钮,第' + (data.index + 1) + '张图片'); 142 | }, 143 | fail: function(err) { 144 | console.log(err.errMsg); 145 | } 146 | } 147 | }) 148 | break 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /utils/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取href页面参数key 3 | */ 4 | export const getQueryString = key => { 5 | // 方法一:正则 6 | var reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)') 7 | var r = window.location.search.substr(1).match(reg) 8 | if (r != null) return unescape(r[2]) 9 | return null 10 | 11 | // 方法二:字符串 12 | // var url = window.location.search; 13 | // var theRequest = new Object(); 14 | // if (url.indexOf("?") != -1) { 15 | // var str = url.substr(1); 16 | // var strs = str.split("&"); 17 | // for (var i = 0; i < strs.length; i++) { 18 | // theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1]); 19 | // } 20 | // } 21 | } 22 | 23 | /** 24 | * 获取url中的参数key 25 | */ 26 | export const getQueryString4Url = (url, key) => { 27 | const reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)') 28 | const r = url.substr(url.indexOf('?') + 1).match(reg) 29 | if (r != null) return unescape(r[2]) 30 | return null 31 | } 32 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | crypto-js@^4.0.0: 6 | version "4.0.0" 7 | resolved "https://registry.npm.taobao.org/crypto-js/download/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" 8 | integrity sha1-KQSrJnep0EKFai6i74DekuSjbcw= 9 | 10 | lodash@^4.17.21: 11 | version "4.17.21" 12 | resolved "https://registry.npm.taobao.org/lodash/download/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 13 | integrity sha1-Z5WRxWTDv/quhFTPCz3zcMPWkRw= 14 | 15 | vue-i18n@^8.23.0: 16 | version "8.23.0" 17 | resolved "https://registry.npm.taobao.org/vue-i18n/download/vue-i18n-8.23.0.tgz#4a65681a1dfe716d47e1d00ddbd6e0b88ea36735" 18 | integrity sha1-SmVoGh3+cW1H4dAN29bguI6jZzU= 19 | --------------------------------------------------------------------------------