commit 53af2dd84ef1d243c18a83564d7c315a4b45f4e4 Author: 陆亚狮 <2696699791@qq.com> Date: Mon Apr 7 09:30:11 2025 +0800 init diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..f21e65c --- /dev/null +++ b/App.vue @@ -0,0 +1,63 @@ + + diff --git a/api/api.js b/api/api.js new file mode 100644 index 0000000..f1bd11f --- /dev/null +++ b/api/api.js @@ -0,0 +1,37 @@ +import request from '@/utils/request' +//使用说明 +// export const getList = data => request.get('/api/list', data, false) +// 页面调用名 请求参数 请求类型 接口地址 loading是否显示 +/*页面中调用方法:(若无请求参数则留空,例:this.$api.getList()) +this.$api.getList(params).then(res => { + +}) +*/ +//列表 +export const getList = data => request.get('/api/list', data) +//登陆 +export const login = data => request.post('/api/login', data) +//注册 +export const register = data => request.post('/api/register', data) +//忘记密码 +export const forgetPassWord = data => request.post('/api/forgetPassWord', data) +//登陆用户信息 +export const baseInfo = data => request.get('/api/baseinfo', data) +//检测版本升级 +export const checkVersion = data => request.post('/api/checkVersion2', data,false) +//文章详情 +export const detail = data => request.get('/api/detail', data,false) +//获取单页内容 +export const page = data => request.get('/api/page', data) +//提交实名认证资料 +export const auth = data => request.post('/api/auth', data) +//修改密码 +export const password = data => request.post('/api/password', data) +//注销帐号 +export const logout_account = data => request.post('/api/logout_account', data) +//修改手机号 +export const phoneBind = data => request.get('/api/phoneBind', data) +//修改邮箱 +export const emailBind = data => request.get('/api/emailBind', data) +//提交反馈 +export const feedback = data => request.post('/api/feedback', data) diff --git a/api/env.js b/api/env.js new file mode 100644 index 0000000..d256ed8 --- /dev/null +++ b/api/env.js @@ -0,0 +1,11 @@ +let BASE_URL +//开发环境中 +if (process.env.NODE_ENV === 'development') { + // 开发环境 + BASE_URL = 'https://mock.apifox.cn/m1/3553664-0-default' //开发环境请求地址 +} else { + // 生产环境 + BASE_URL = 'https://mock.apifox.cn/m1/3553664-0-default' //生成环境请求地址 +} + +export default BASE_URL \ No newline at end of file diff --git a/components/appUpdate/appUpdate.js b/components/appUpdate/appUpdate.js new file mode 100644 index 0000000..0bbbf94 --- /dev/null +++ b/components/appUpdate/appUpdate.js @@ -0,0 +1,790 @@ +export default function(updateInfo) { + updateInfo.platform = updateInfo.platform ? updateInfo.platform : 'android' + updateInfo.mainColor = updateInfo.mainColor ? updateInfo.mainColor : 'FF5B78' + if (updateInfo.platform == 'android' || updateInfo.platform == 'ios') { + + } else { + return false + } + let maskLayer = new plus.nativeObj.View('maskLayer', { + top: '0px', + left: '0px', + height: '100%', + width: '100%', + backgroundColor: 'rgba(0,0,0,0.5)' + }) + let screenWidth = plus.screen.resolutionWidth + let screenHeight = plus.screen.resolutionHeight + const popupViewWidth = screenWidth * 0.7 + const viewContentPadding = 20 + const viewContentWidth = parseInt(popupViewWidth - (viewContentPadding * 2)) + const descriptionList = drawtext((updateInfo.updateContent || '发现新版本'), viewContentWidth) + let popupViewHeight = 80 + 20 + 20 + 90 + 50 + let popupViewContentList = [{ + src: '/static/images/update.png', + id: "logo", + tag: "img", + position: { + top: "0px", + left: (popupViewWidth - 124) / 2 + "px", + width: "124px", + height: "80px", + } + }, + { + tag: 'font', + id: 'title', + text: "发现新版本 " + (updateInfo.version || ''), + textStyles: { + size: '18px', + color: "#333", + weight: "bold", + whiteSpace: "normal" + }, + position: { + top: '90px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "30px", + } + } + ] + const textHeight = 18 + let contentTop = 130 + descriptionList.forEach((item, index) => { + if (index > 0) { + popupViewHeight += textHeight; + contentTop += textHeight; + } + popupViewContentList.push({ + tag: 'font', + id: 'content' + index + 1, + text: item.content, + textStyles: { + size: '14px', + color: "#666", + lineSpacing: "50%", + align: "left" + }, + position: { + top: contentTop + "px", + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: textHeight + "px", + } + }); + if (item.type == "break") { + contentTop += 10; + popupViewHeight += 10; + } + }) + if (updateInfo.force) { + popupViewContentList.push({ + tag: 'rect', //绘制底边按钮 + rectStyles: { + radius: "6px", + color: updateInfo.mainColor + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "40px" + } + }) + popupViewContentList.push({ + tag: 'font', + id: 'confirmText', + text: "立即升级", + textStyles: { + size: '16px', + color: "#FFF", + lineSpacing: "0%", + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "40px" + } + }) + } else { + popupViewContentList.push({ + tag: 'rect', + id: 'cancelBox', + rectStyles: { + radius: "3px", + borderColor: "#f1f1f1", + borderWidth: "1px", + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + }) + popupViewContentList.push({ + tag: 'rect', + id: 'confirmBox', + rectStyles: { + radius: "3px", + color: updateInfo.mainColor, + }, + position: { + bottom: viewContentPadding + 'px', + left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + }) + popupViewContentList.push({ + tag: 'font', + id: 'cancelText', + text: "暂不升级", + textStyles: { + size: '16px', + color: "#666", + lineSpacing: "0%", + whiteSpace: "normal" + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + }) + popupViewContentList.push({ + tag: 'font', + id: 'confirmText', + text: "立即升级", + textStyles: { + size: '16px', + color: "#FFF", + lineSpacing: "0%", + whiteSpace: "normal" + }, + position: { + bottom: viewContentPadding + 'px', + left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + }) + } + let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单 + tag: "rect", + top: (screenHeight - popupViewHeight) / 2 + "px", + left: '15%', + height: popupViewHeight + "px", + width: "70%" + }) + popupView.drawRect({ + color: "#FFFFFF", + radius: "8px" + }, { + top: "40px", + height: popupViewHeight - 40 + "px", + }) + popupView.draw(popupViewContentList) + popupView.addEventListener("click", e => { + let maxTop = popupViewHeight - viewContentPadding + let maxLeft = popupViewWidth - viewContentPadding + let buttonWidth = (viewContentWidth - viewContentPadding) / 2 + if (e.clientY > maxTop - 30 && e.clientY < maxTop) { + if (updateInfo.force) { + if (e.clientX > viewContentPadding && e.clientX < maxLeft) { + maskLayer.hide() + popupView.hide() + let platform = updateInfo.platform || 'android' + let downUrl = updateInfo.downUrl || '' + download(updateInfo) + } + } else { + let maxTop = popupViewHeight - viewContentPadding; + let maxLeft = popupViewWidth - viewContentPadding; + let buttonWidth = (viewContentWidth - viewContentPadding) / 2; + if (e.clientY > maxTop - 30 && e.clientY < maxTop) { + // 暂不升级 + if (e.clientX > viewContentPadding && e.clientX < maxLeft - buttonWidth - + viewContentPadding) { + maskLayer.hide() + popupView.hide() + } + if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { + // 立即升级 + maskLayer.hide() + popupView.hide() + let platform = updateInfo.platform || 'android' + let downUrl = updateInfo.downUrl || '' + download(updateInfo) + } + } + } + } + }) + // 点击遮罩层 + maskLayer.addEventListener("click", function() { //处理遮罩层点击 + // maskLayer.hide(); + // popupView.hide(); + }) + // 显示弹窗 + maskLayer.show() + popupView.show() +} + +// 下载流程 +function download(updateInfo) { + let platform = updateInfo.platform || 'android' + if (updateInfo.downUrl) { + if (platform == 'ios') { + plus.runtime.openURL(updateInfo.downUrl) + } + if (platform == 'android') { + getDownload(updateInfo) + } + } else { + plus.nativeUI.alert('下载地址无效') + } +} + +// 从服务器下载应用资源包(wgt文件) +const getDownload = function(data) { + let dtask + let popupData = { + progress: true, + buttonNum: 2 + }; + if(data.force){ + popupData.buttonNum = 0 + } + + let lastProgressValue = 0 + let popupObj = downloadPopup(popupData, data.mainColor) + + dtask = plus.downloader.createDownload(data.downUrl, { + filename: "_doc/update/" + }, function(download, status) { + if (status == 200) { + popupObj.change({ + progressValue: 100, + progressTip:"正在安装文件...", + progress: true, + buttonNum: 0 + }) + plus.runtime.install(download.filename, {}, function() { + popupObj.change({ + contentText: "应用资源更新完成!", + buttonNum: 1, + progress: false + }); + }, function(e) { + popupObj.cancel() + plus.nativeUI.alert("安装文件失败[" + e.code + "]:" + e.message); + }); + } else { + popupObj.change({ + contentText: "文件下载失败...", + buttonNum: 1, + progress: false + }); + } + }); + dtask.start() + dtask.addEventListener("statechanged", function(task, status) { + switch (task.state) { + case 1: // 开始 + popupObj.change({ + progressValue:0, + progressTip:"准备下载...", + progress: true + }); + break; + case 2: // 已连接到服务器 + popupObj.change({ + progressValue:0, + progressTip:"开始下载...", + progress: true + }); + break; + case 3: + const progress = parseInt(task.downloadedSize / task.totalSize * 100); + if(progress - lastProgressValue >= 2){ + lastProgressValue = progress; + popupObj.change({ + progressValue:progress, + progressTip: "已下载" + progress + "%", + progress: true + }); + } + break; + } + }) + // 取消下载 + popupObj.cancelDownload = function(){ + dtask && dtask.abort() + uni.showToast({ + title: "已取消下载", + icon:"none" + }); + } + // 重启APP + popupObj.reboot = function(){ + plus.runtime.restart() + } +} + +// 文件下载的弹窗绘图 +function downloadPopupDrawing(data, mainColor){ + // 以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心 + const screenWidth = plus.screen.resolutionWidth; + const screenHeight = plus.screen.resolutionHeight; + //弹窗容器宽度 + const popupViewWidth = screenWidth * 0.7; + + // 弹窗容器的Padding + const viewContentPadding = 20; + // 弹窗容器的宽度 + const viewContentWidth = popupViewWidth - (viewContentPadding * 2); + // 弹窗容器高度 + let popupViewHeight = viewContentPadding * 3 + 60; + let progressTip = data.progressTip || "准备下载..."; + let contentText = data.contentText || "正在为您更新,请耐心等待"; + + let elementList = [ + { + tag: 'rect', //背景色 + color: '#FFFFFF', + rectStyles:{ + radius: "8px" + } + }, + { + tag: 'font', + id: 'title', + text: "升级APP", + textStyles: { + size: '16px', + color: "#333", + weight: "bold", + verticalAlign: "middle", + whiteSpace: "normal" + }, + position: { + top: viewContentPadding + 'px', + height: "30px", + } + }, + { + tag: 'font', + id: 'content', + text: contentText, + textStyles: { + size: '14px', + color: "#333", + verticalAlign: "middle", + whiteSpace: "normal" + }, + position: { + top: viewContentPadding * 2 + 30 + 'px', + height: "20px", + } + } + ]; + // 是否有进度条 + + if(data.progress){ + popupViewHeight += viewContentPadding + 40 + elementList = elementList.concat([ + { + tag: 'font', + id: 'progressValue', + text: progressTip, + textStyles: { + size: '14px', + color: mainColor, + whiteSpace: "normal" + }, + position: { + top: viewContentPadding * 4 + 20 + 'px', + height: "30px" + } + }, + { + tag: 'rect', //绘制进度条背景 + id: 'progressBg', + rectStyles:{ + radius: "4px", + borderColor: "#f1f1f1", + borderWidth: "1px", + }, + position:{ + top: viewContentPadding * 4 + 60 + 'px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "8px" + } + }, + ]) + } + + if (data.buttonNum == 2) { + popupViewHeight += viewContentPadding + 30; + elementList = elementList.concat([ + { + tag: 'rect', //绘制底边按钮 + rectStyles:{ + radius: "3px", + borderColor: "#f1f1f1", + borderWidth: "1px", + }, + position:{ + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px" + } + }, + { + tag: 'rect', //绘制底边按钮 + rectStyles:{ + radius: "3px", + color: mainColor + }, + position:{ + bottom: viewContentPadding + 'px', + left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px" + } + }, + { + tag: 'font', + id: 'cancelText', + text: "取消下载", + textStyles: { + size: '14px', + color: "#666", + lineSpacing: "0%", + whiteSpace: "normal" + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + }, + { + tag: 'font', + id: 'confirmText', + text: "后台下载", + textStyles: { + size: '14px', + color: "#FFF", + lineSpacing: "0%", + whiteSpace: "normal" + }, + position: { + bottom: viewContentPadding + 'px', + left: ((viewContentWidth - viewContentPadding) / 2 + viewContentPadding * 2) + "px", + width: (viewContentWidth - viewContentPadding) / 2 + "px", + height: "40px", + } + } + ]); + } + + if (data.buttonNum == 1) { + popupViewHeight += viewContentPadding + 40; + elementList = elementList.concat([ + { + tag: 'rect', //绘制底边按钮 + rectStyles:{ + radius: "6px", + color: $mainColor + }, + position:{ + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "40px" + } + }, + { + tag: 'font', + id: 'confirmText', + text: "关闭", + textStyles: { + size: '14px', + color: "#FFF", + lineSpacing: "0%", + }, + position: { + bottom: viewContentPadding + 'px', + left: viewContentPadding + "px", + width: viewContentWidth + "px", + height: "40px" + } + } + ]); + } + + return { + popupViewHeight:popupViewHeight, + popupViewWidth:popupViewWidth, + screenHeight:screenHeight, + viewContentWidth:viewContentWidth, + viewContentPadding:viewContentPadding, + elementList: elementList + }; +} +// 文件下载的弹窗 +function downloadPopup(data, mainColor) { + + // 弹窗遮罩层 + let maskLayer = new plus.nativeObj.View("maskLayer", { //先创建遮罩层 + top: '0px', + left: '0px', + height: '100%', + width: '100%', + backgroundColor: 'rgba(0,0,0,0.5)' + }); + + let popupViewData = downloadPopupDrawing(data, mainColor); + + // 弹窗内容 + let popupView = new plus.nativeObj.View("popupView", { //创建底部图标菜单 + tag: "rect", + top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", + left: '15%', + height: popupViewData.popupViewHeight + "px", + width: "70%", + }); + let progressValue = 0; + let progressTip = 0; + let contentText = 0; + let buttonNum = 2; + if(data.buttonNum >= 0){ + buttonNum = data.buttonNum; + } + popupView.draw(popupViewData.elementList); + let callbackData = { + change: function(res) { + let progressElement = []; + if(res.progressValue){ + progressValue = res.progressValue; + // 绘制进度条 + progressElement.push({ + tag: 'rect', //绘制进度条背景 + id: 'progressValueBg', + rectStyles:{ + radius: "4px", + color: mainColor + }, + position:{ + top: popupViewData.viewContentPadding * 4 + 60 + 'px', + left: popupViewData.viewContentPadding + "px", + width: popupViewData.viewContentWidth * (res.progressValue / 100) + "px", + height: "8px" + } + }); + } + if(res.progressTip){ + progressTip = res.progressTip; + progressElement.push({ + tag: 'font', + id: 'progressValue', + text: res.progressTip, + textStyles: { + size: '14px', + color: mainColor, + whiteSpace: "normal" + }, + position: { + top: popupViewData.viewContentPadding * 4 + 20 + 'px', + height: "30px" + } + }); + } + if(res.contentText){ + contentText = res.contentText; + progressElement.push({ + tag: 'font', + id: 'content', + text: res.contentText, + textStyles: { + size: '16px', + color: "#333", + whiteSpace: "normal" + }, + position: { + top: popupViewData.viewContentPadding * 2 + 30 + 'px', + height: "30px", + } + }); + } + if(res.buttonNum >= 0 && buttonNum != res.buttonNum){ + buttonNum = res.buttonNum; + popupView.reset(); + popupViewData = downloadPopupDrawing(Object.assign({ + progressValue:progressValue, + progressTip:progressTip, + contentText:contentText, + },res)); + let newElement = []; + popupViewData.elementList.map((item,index) => { + let have = false; + progressElement.forEach((childItem,childIndex) => { + if(item.id == childItem.id){ + have = true; + } + }); + if(!have){ + newElement.push(item); + } + }); + progressElement = newElement.concat(progressElement); + popupView.setStyle({ + tag: "rect", + top: (popupViewData.screenHeight - popupViewData.popupViewHeight) / 2 + "px", + left: '15%', + height: popupViewData.popupViewHeight + "px", + width: "70%", + }); + popupView.draw(progressElement); + }else{ + popupView.draw(progressElement); + } + }, + cancel: function() { + maskLayer.hide(); + popupView.hide(); + } + } + popupView.addEventListener("click", function(e) { + let maxTop = popupViewData.popupViewHeight - popupViewData.viewContentPadding; + let maxLeft = popupViewData.popupViewWidth - popupViewData.viewContentPadding; + if (e.clientY > maxTop - 40 && e.clientY < maxTop) { + if(buttonNum == 1){ + // 单按钮 + if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft) { + maskLayer.hide(); + popupView.hide(); + callbackData.reboot(); + } + }else if(buttonNum == 2){ + // 双按钮 + let buttonWidth = (popupViewData.viewContentWidth - popupViewData.viewContentPadding) / 2; + if (e.clientX > popupViewData.viewContentPadding && e.clientX < maxLeft - buttonWidth - popupViewData.viewContentPadding) { + maskLayer.hide(); + popupView.hide(); + callbackData.cancelDownload(); + } else if (e.clientX > maxLeft - buttonWidth && e.clientX < maxLeft) { + maskLayer.hide(); + popupView.hide(); + } + } + } + }); + // 显示弹窗 + maskLayer.show(); + popupView.show(); + // 改变进度条 + return callbackData +} + +// 文字换行 +function drawtext(text, maxWidth) { + let textArr = text.split(""); + let len = textArr.length; + // 上个节点 + let previousNode = 0; + // 记录节点宽度 + let nodeWidth = 0; + // 文本换行数组 + let rowText = []; + // 如果是字母,侧保存长度 + let letterWidth = 0; + // 汉字宽度 + let chineseWidth = 14; + // otherFont宽度 + let otherWidth = 7; + for (let i = 0; i < len; i++) { + if (/[\u4e00-\u9fa5]|[\uFE30-\uFFA0]/g.test(textArr[i])) { + if (letterWidth > 0) { + if (nodeWidth + chineseWidth + letterWidth * otherWidth > maxWidth) { + rowText.push({ + type: "text", + content: text.substring(previousNode, i) + }); + previousNode = i + nodeWidth = chineseWidth + letterWidth = 0 + } else { + nodeWidth += chineseWidth + letterWidth * otherWidth + letterWidth = 0 + } + } else { + if (nodeWidth + chineseWidth > maxWidth) { + rowText.push({ + type: "text", + content: text.substring(previousNode, i) + }) + previousNode = i + nodeWidth = chineseWidth + } else { + nodeWidth += chineseWidth + } + } + } else { + if (/\n/g.test(textArr[i])) { + rowText.push({ + type: "break", + content: text.substring(previousNode, i) + }) + previousNode = i + 1 + nodeWidth = 0 + letterWidth = 0 + } else if (textArr[i] == "\\" && textArr[i + 1] == "n") { + rowText.push({ + type: "break", + content: text.substring(previousNode, i) + }) + previousNode = i + 2 + nodeWidth = 0 + letterWidth = 0 + } else if (/[a-zA-Z0-9]/g.test(textArr[i])) { + letterWidth += 1; + if (nodeWidth + letterWidth * otherWidth > maxWidth) { + rowText.push({ + type: "text", + content: text.substring(previousNode, i + 1 - letterWidth) + }) + previousNode = i + 1 - letterWidth + nodeWidth = letterWidth * otherWidth + letterWidth = 0 + } + } else { + if (nodeWidth + otherWidth > maxWidth) { + rowText.push({ + type: "text", + content: text.substring(previousNode, i) + }); + previousNode = i + nodeWidth = otherWidth + } else { + nodeWidth += otherWidth + } + } + } + } + if (previousNode < len) { + rowText.push({ + type: "text", + content: text.substring(previousNode, len) + }) + } + return rowText +} \ No newline at end of file diff --git a/components/daySelect/index.vue b/components/daySelect/index.vue new file mode 100644 index 0000000..3ed2e56 --- /dev/null +++ b/components/daySelect/index.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..6d3c1fc --- /dev/null +++ b/config/config.js @@ -0,0 +1,17 @@ + +let config = { + //不拦截页面路径 + whiteList: [ + '/pages/public/login', + '/pages/public/forget_password', + '/pages/public/register', + '/pages/index/index', + '/pages/index/list', + '/pages/subPack/index/detail', + //一行一个 + ], + token : 'token', //本地存储token的变量名 + login_page : '/pages/public/login', //拦截后跳转的登陆页路径 + vconsole_status: 0, //是否启用调试工具,1为启用,0不启用 +} +export default config \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..c3ff205 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..3472935 --- /dev/null +++ b/main.js @@ -0,0 +1,47 @@ +import App from './App' +import '@/utils/interceptor.js';//引入拦截 + +import Vue from 'vue' +import uView from '@/uni_modules/uview-ui' +Vue.use(uView) + +import store from './store' + +// 注册请求方法 +import * as api from '@/api/api.js' +Vue.prototype.$api = api + + +import BASE_URL from '@/api/env.js' //引入接口共用地址 +Vue.prototype.$API_BASE_URL= BASE_URL + +import config from '@/config/config.js'; +Vue.prototype.$systemConfig = config + +let vuexStore = require("@/store/$u.mixin.js"); +Vue.mixin(vuexStore); + +//是否启用调试工具 +if(config.vconsole_status){ + const vconsole = require('vconsole') + Vue.prototype.$vconsole = new vconsole() // 使用vconsole +} + + +Vue.config.productionTip = false +App.mpType = 'app' +const app = new Vue({ + store, + ...App +}) +app.$mount() + +// #ifdef VUE3 +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} +// #endif \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..c1671fd --- /dev/null +++ b/manifest.json @@ -0,0 +1,91 @@ +{ + "name" : "巡检保洁", + "appid" : "", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + /* 5+App特有相关 */ + "app-plus" : { + "usingComponents" : true, + "nvueStyleCompiler" : "uni-app", + "compilerVersion" : 3, + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + /* 模块配置 */ + "modules" : { + "Camera" : {} + }, + /* 应用发布信息 */ + "distribute" : { + /* android打包配置 */ + "android" : { + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + /* ios打包配置 */ + "ios" : {}, + /* SDK配置 */ + "sdkConfigs" : { + "geolocation" : { + "system" : { + "__platform__" : [ "ios" ] + } + } + }, + "splashscreen" : { + "androidStyle" : "default" + } + } + }, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false, + "postcss" : true, + "minified" : true + }, + "usingComponents" : true, + "optimization" : { + "subPackages" : true + } + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "2" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b2277a2 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "id": "liuyan814-unitempate", + "name": "巡检保洁", + "displayName": "巡检保洁", + "version": "1.0.6", + "description": "快速搭建uni-app项目,封装request,带页面拦截器,请求锁,带请求loading,集成uview,集成在线更新组件、集成调试工具vconsole,分包。", + "keywords": [ + "封装request", + "页面拦截器", + "uview", + "图鸟UI", + "在线更新" + ], + "dcloudext": { + "category": [ + "uni-app前端模板", + "前端页面模板" + ] + } +} \ No newline at end of file diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..72802a3 --- /dev/null +++ b/pages.json @@ -0,0 +1,222 @@ +{ + // 如果您是通过uni_modules形式引入uView,可以忽略此配置 + "easycom": { + "^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue" + }, + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "首页" + } + }, + { + "path": "pages/index/list", + "style": { + "navigationBarTitleText": "保洁点位" + } + }, + { + "path": "pages/index/cleanDetails", + "style": { + "navigationStyle": "custom" , + "navigationBarTextStyle": "black", + "navigationBarTitleText": "保洁详情" + } + }, + { + "path": "pages/index/cleanPlan", + "style": { + "navigationStyle": "custom" , + "navigationBarTextStyle": "black", + "navigationBarTitleText": "保洁计划" + } + }, + { + "path": "pages/index/planList", + "style": { + "navigationStyle": "custom" , + "navigationBarTextStyle": "black", + "navigationBarTitleText": "计划列表" + } + }, + { + "path": "pages/my/index", + "style": { + "navigationBarTitleText": "我的", + "navigationBarBackgroundColor": "#4790FF", + "navigationBarTextStyle": "white", + "enablePullDownRefresh": true + + } + }, + { + "path": "pages/public/login", + "style": { + "navigationBarTitleText": "登录" + } + }, + { + "path" : "pages/my/profile", + "style" : + { + "navigationBarTitleText" : "个人信息", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/setting", + "style" : + { + "navigationBarTitleText" : "设置", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/password", + "style" : + { + "navigationBarTitleText" : "修改密码", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/index", + "style" : + { + "navigationBarTitleText" : "帐号&安全", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/about/index", + "style" : + { + "navigationBarTitleText" : "关于我们", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/phone", + "style" : + { + "navigationBarTitleText" : "手机号绑定", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/email", + "style" : + { + "navigationBarTitleText" : "邮箱绑定", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/account/logout_account", + "style" : + { + "navigationBarTitleText" : "注销帐号", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/auth", + "style" : + { + "navigationBarTitleText" : "实名认证", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/public/page", + "style" : + { + "navigationBarTitleText" : "", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/my/about/feedback", + "style" : + { + "navigationBarTitleText" : "意见反馈", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/public/register", + "style" : + { + "navigationBarTitleText" : "注册", + "enablePullDownRefresh" : false + } + }, + { + "path" : "pages/public/forget_password", + "style" : + { + "navigationBarTitleText" : "忘记密码", + "enablePullDownRefresh" : false + } + } + + ], + //分包加载配置,此配置为小程序的分包加载机制。 + "subPackages": [ + { + "root": "pages/subPack", //子包的根目录 + "pages": [ + { + "path": "index/detail", + "style": { + "navigationBarTitleText": "详情" + } + } + + ] + } + ], + + "globalStyle": { + "navigationBarTextStyle": "#211D2F", + "navigationBarTitleText": "uni-app", + "navigationBarBackgroundColor": "white", + "backgroundColor": "#F8F8F8" + }, + "tabBar": { + "color": "#2D2D2D", + "selectedColor": "#4183F8", + "borderStyle": "white", + "list": [ + { + "pagePath": "pages/index/index", + "iconPath": "static/tab/home.png", + "selectedIconPath": "static/tab/home_cur.png", + "text": "首页" + }, + { + "pagePath": "pages/index/list", + "iconPath": "static/tab/list.png", + "selectedIconPath": "static/tab/list_cur.png", + "text": "计划" + }, + { + "pagePath": "pages/my/index", + "iconPath": "static/tab/my.png", + "selectedIconPath": "static/tab/my_cur.png", + "text": "我的" + } + ] + }, + + // 分包预载配置 + "preloadRule": { + // 当我们进入了pages/index/index页面以后就会预下载pages/subPack分包 + "pages/index/index": { + "network": "all", //在指定网络下预下载,可选值为:all(不限网络)、wifi(仅wifi下预下载) + "packages": ["pages/subPack"] //进入页面后预下载分包 + } + }, + "uniIdRouter": {} +} diff --git a/pages/detail.vue b/pages/detail.vue new file mode 100644 index 0000000..5309fa1 --- /dev/null +++ b/pages/detail.vue @@ -0,0 +1,155 @@ + + + diff --git a/pages/form.vue b/pages/form.vue new file mode 100644 index 0000000..4e085d2 --- /dev/null +++ b/pages/form.vue @@ -0,0 +1,205 @@ + + + + + + diff --git a/pages/index/cleanDetails.vue b/pages/index/cleanDetails.vue new file mode 100644 index 0000000..9607b9a --- /dev/null +++ b/pages/index/cleanDetails.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/pages/index/cleanPlan.vue b/pages/index/cleanPlan.vue new file mode 100644 index 0000000..cf31785 --- /dev/null +++ b/pages/index/cleanPlan.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/pages/index/index.vue b/pages/index/index.vue new file mode 100644 index 0000000..ec990c1 --- /dev/null +++ b/pages/index/index.vue @@ -0,0 +1,173 @@ + + + diff --git a/pages/index/list.vue b/pages/index/list.vue new file mode 100644 index 0000000..32292c3 --- /dev/null +++ b/pages/index/list.vue @@ -0,0 +1,170 @@ + + + + + diff --git a/pages/index/planList.vue b/pages/index/planList.vue new file mode 100644 index 0000000..4c85a11 --- /dev/null +++ b/pages/index/planList.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/pages/my/about/feedback.vue b/pages/my/about/feedback.vue new file mode 100644 index 0000000..900d697 --- /dev/null +++ b/pages/my/about/feedback.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/pages/my/about/index.vue b/pages/my/about/index.vue new file mode 100644 index 0000000..4ab3592 --- /dev/null +++ b/pages/my/about/index.vue @@ -0,0 +1,69 @@ + + + diff --git a/pages/my/account/email.vue b/pages/my/account/email.vue new file mode 100644 index 0000000..46a2112 --- /dev/null +++ b/pages/my/account/email.vue @@ -0,0 +1,116 @@ + + + + \ No newline at end of file diff --git a/pages/my/account/index.vue b/pages/my/account/index.vue new file mode 100644 index 0000000..32a5112 --- /dev/null +++ b/pages/my/account/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/pages/my/account/logout_account.vue b/pages/my/account/logout_account.vue new file mode 100644 index 0000000..019b7d7 --- /dev/null +++ b/pages/my/account/logout_account.vue @@ -0,0 +1,82 @@ + + + diff --git a/pages/my/account/password.vue b/pages/my/account/password.vue new file mode 100644 index 0000000..18e125d --- /dev/null +++ b/pages/my/account/password.vue @@ -0,0 +1,73 @@ + + + + \ No newline at end of file diff --git a/pages/my/account/phone.vue b/pages/my/account/phone.vue new file mode 100644 index 0000000..ca6e940 --- /dev/null +++ b/pages/my/account/phone.vue @@ -0,0 +1,120 @@ + + + + \ No newline at end of file diff --git a/pages/my/account/setting.vue b/pages/my/account/setting.vue new file mode 100644 index 0000000..624c6bd --- /dev/null +++ b/pages/my/account/setting.vue @@ -0,0 +1,38 @@ + + + diff --git a/pages/my/auth.vue b/pages/my/auth.vue new file mode 100644 index 0000000..bf21e37 --- /dev/null +++ b/pages/my/auth.vue @@ -0,0 +1,108 @@ + + + + + + diff --git a/pages/my/index.vue b/pages/my/index.vue new file mode 100644 index 0000000..edc8e70 --- /dev/null +++ b/pages/my/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/pages/my/profile.vue b/pages/my/profile.vue new file mode 100644 index 0000000..efa333c --- /dev/null +++ b/pages/my/profile.vue @@ -0,0 +1,70 @@ + + + diff --git a/pages/public/forget_password.vue b/pages/public/forget_password.vue new file mode 100644 index 0000000..790063e --- /dev/null +++ b/pages/public/forget_password.vue @@ -0,0 +1,130 @@ + + + + \ No newline at end of file diff --git a/pages/public/login.vue b/pages/public/login.vue new file mode 100644 index 0000000..7837d74 --- /dev/null +++ b/pages/public/login.vue @@ -0,0 +1,120 @@ + + + + \ No newline at end of file diff --git a/pages/public/page.vue b/pages/public/page.vue new file mode 100644 index 0000000..249e992 --- /dev/null +++ b/pages/public/page.vue @@ -0,0 +1,49 @@ + + + diff --git a/pages/public/register.vue b/pages/public/register.vue new file mode 100644 index 0000000..b818145 --- /dev/null +++ b/pages/public/register.vue @@ -0,0 +1,179 @@ + + + + + + diff --git a/pages/subPack/index/detail.vue b/pages/subPack/index/detail.vue new file mode 100644 index 0000000..c2b5421 --- /dev/null +++ b/pages/subPack/index/detail.vue @@ -0,0 +1,136 @@ + + + \ No newline at end of file diff --git a/pages/tab_list.vue b/pages/tab_list.vue new file mode 100644 index 0000000..3516ca9 --- /dev/null +++ b/pages/tab_list.vue @@ -0,0 +1,73 @@ + + + \ No newline at end of file diff --git a/pages/templates.vue b/pages/templates.vue new file mode 100644 index 0000000..0df9ba1 --- /dev/null +++ b/pages/templates.vue @@ -0,0 +1,31 @@ + + + \ No newline at end of file diff --git a/pages/─ú░σ─┐┬╝.txt b/pages/─ú░σ─┐┬╝.txt new file mode 100644 index 0000000..9dbcfbc --- /dev/null +++ b/pages/─ú░σ─┐┬╝.txt @@ -0,0 +1 @@ +可把模板放在 \HBuilder X\templates\file\vue-page目录下面,这样可以在新建页面中,快速创建。 \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..94da6f9 --- /dev/null +++ b/readme.md @@ -0,0 +1,131 @@ +## 项目说明 + +本项目可以快速搭建uni-app项目,封装request,集成z-paging(下拉刷新,上拉加载),带页面拦截器,请求锁,请求loading,集成uview,集成在线更新组件,集成本地调试工具vconsole、分包。 + +## 接口开发 + +接口服务器地址文件:**api/env.js**,修改服务器地址: + +```json +let BASE_URL +//开发环境中 +if (process.env.NODE_ENV === 'development') { + // 开发环境 + BASE_URL = 'https://mock.apifox.com/m1/3553664-0-default' //开发环境请求地址 +} else { + // 生产环境 + BASE_URL = 'https://mock.apifox.com/m1/3553664-0-default' //生成环境请求地址 +} + +export default BASE_URL +``` + +接口文件:**api/api.js**,在里面定义相关接口地址即可。 + +```json +//列表,GET方式请求 +export const getList = data => request.get('/api/list', data) +//登陆,POST方式请求 +export const login = data => request.post('/api/login', data) +//检测版本升级,POST方式请求,第三个参数(false)不显示loading加载 +export const checkVersion = data => request.post('/api/checkVersion', data,false) +``` + +页面中调用的方法: + +GET或POST调用如下:(无须导入接口方法,已在全局文件main.js中引入) + +```json +//请求参数 +const params = { + page: pageNo, + limit: pageSize +} +//getList即对应api.js中的接口 +this.$api.getList(params).then(res => { +    //todo.... +}) +``` + +## 页面拦截器 + +本项目集成了页面拦截器,页面拦截器即表示在白名单的页面URL不拦截,其他页面则拦截。 + +场景:办公OA APP,只有登陆页面不需要拦截,其他页面则需要拦截。 + +拦截器与token搭配使用,若是本地无TOKE,APP启动时打开首页,则自动会跳转到登陆页。若本地存在TOKEN,则打开首页时不会再要求登陆。 + +页面拦截器配置文件在**config/config.js** + +以OA为例,登陆页不需要拦截则设置如下,还可添加其他不需要拦截的页面。 + +```json +let config = { + //不拦截页面路径 + whiteList: [ + '/pages/public/login' + ], + token : 'token', //本地存储token的变量名 + login_page : '/pages/public/login' //拦截后跳转的登陆页路径 +} +``` + + 注:封装请求文件(/utils/request.js)中,若服务器返回错误码大于400,则也跳转登陆页。 + +``` +if(res.code > 400){ + clearStorageSync('token') +    useRouter(systemConfig.login_page, 'reLaunch') +}else{ + reslove(res) +} +``` + +## 在线升级 + +本项目集成了在线升级组件(仅支持APP),文件在:**components/appUpdate**,是否更新需要服务器返回如下JSON信息: + +```json +{ + "code": 0, + "platform": "android", + "version": "1.3.0", + "downUrl": "下载地址", + "updateContent": "修复BUG", + "force" : 0 + } +``` + +> code为0时会提示更新,1则不弹出更新窗口。 +> +> 测试更新弹出窗口,可修改api.js中的检测更新接口地址为:/api/checkVersion2 + +## 本地调试工具vconsole + +本项目集成了本地调试工具vconsole,主要用于在生成APP或小程序后方便调试检查错误。 + +若不需要开启,可以修改配置文件config/config.js是否开启: + +`vconsole_status: 1, //是否启用调试工具,1为启用,0不启用` + +## 分包 + + 增加分包功能,分包功能主要针对微信小程序,分包配置在pages.json中subPackages段内。如果不需要分包可以删除,并修改manifest.json 中"mp-weixin“段中 ` "subPackages" : true` 删除。 + +## 集成UI + +本项目含集成了uview,z-paging,具体使用方法见官方文档。 + +uview官方文档:[介绍 | uView - 多平台快速开发的UI框架 - uni-app UI框架](https://v1.uviewui.com/components/intro.html) + +z-paging官方文档:[介绍 | z-paging文档](https://z-paging.zxlee.cn/start/intro.html)(非常方便的实现上拉加载、下拉刷新等功能) + +## 其他说明 + +**下载说明:建议单独下载文件后并解压,并在HB中打开目录进行演示,不要覆盖现在项目。** + +注:本项目中接口服务器和数据为mock数据,运行即可看到数据。 + +登陆: 随机输入用户名和密码都可以登陆成功。 + +如果在使用过程中遇到问题,可以在评论区反馈也可加微信liuyan814。 diff --git a/static/images/area-bg.png b/static/images/area-bg.png new file mode 100644 index 0000000..d1d4119 Binary files /dev/null and b/static/images/area-bg.png differ diff --git a/static/images/list.png b/static/images/list.png new file mode 100644 index 0000000..953d783 Binary files /dev/null and b/static/images/list.png differ diff --git a/static/images/list_cur.png b/static/images/list_cur.png new file mode 100644 index 0000000..30b9857 Binary files /dev/null and b/static/images/list_cur.png differ diff --git a/static/images/login_bg.png b/static/images/login_bg.png new file mode 100644 index 0000000..4297c19 Binary files /dev/null and b/static/images/login_bg.png differ diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 0000000..60091c2 Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/images/plan-bg.png b/static/images/plan-bg.png new file mode 100644 index 0000000..a574622 Binary files /dev/null and b/static/images/plan-bg.png differ diff --git a/static/images/plan-icon-bg.png b/static/images/plan-icon-bg.png new file mode 100644 index 0000000..cf583e3 Binary files /dev/null and b/static/images/plan-icon-bg.png differ diff --git a/static/images/pos-icon.png b/static/images/pos-icon.png new file mode 100644 index 0000000..9db86a4 Binary files /dev/null and b/static/images/pos-icon.png differ diff --git a/static/images/update.png b/static/images/update.png new file mode 100644 index 0000000..33d6c48 Binary files /dev/null and b/static/images/update.png differ diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b2dade8 Binary files /dev/null and b/static/logo.png differ diff --git a/static/tab/home.png b/static/tab/home.png new file mode 100644 index 0000000..3aefa52 Binary files /dev/null and b/static/tab/home.png differ diff --git a/static/tab/home_cur.png b/static/tab/home_cur.png new file mode 100644 index 0000000..e7fe9e6 Binary files /dev/null and b/static/tab/home_cur.png differ diff --git a/static/tab/list.png b/static/tab/list.png new file mode 100644 index 0000000..975a5a0 Binary files /dev/null and b/static/tab/list.png differ diff --git a/static/tab/list_cur.png b/static/tab/list_cur.png new file mode 100644 index 0000000..77a0cd9 Binary files /dev/null and b/static/tab/list_cur.png differ diff --git a/static/tab/my.png b/static/tab/my.png new file mode 100644 index 0000000..cba9f6a Binary files /dev/null and b/static/tab/my.png differ diff --git a/static/tab/my_cur.png b/static/tab/my_cur.png new file mode 100644 index 0000000..67ccfd5 Binary files /dev/null and b/static/tab/my_cur.png differ diff --git a/store/$u.mixin.js b/store/$u.mixin.js new file mode 100644 index 0000000..94ed2ee --- /dev/null +++ b/store/$u.mixin.js @@ -0,0 +1,27 @@ +import { mapState } from 'vuex' +import store from "@/store" + +// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中 +let $uStoreKey = []; +try{ + $uStoreKey = store.state ? Object.keys(store.state) : []; +}catch(e){ + +} + +module.exports = { + created() { + // 将vuex方法挂在到$u中 + // 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗') + // 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1') + this.$u.vuex = (name, value) => { + this.$store.commit('$uStore', { + name,value + }) + } + }, + computed: { + // 将vuex的state中的所有变量,解构到全局混入的mixin中 + ...mapState($uStoreKey) + } +} \ No newline at end of file diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..33ce954 --- /dev/null +++ b/store/index.js @@ -0,0 +1,64 @@ +import Vue from 'vue' +import Vuex from 'vuex' +Vue.use(Vuex) + +let lifeData = {}; + +try{ + // 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的 + lifeData = uni.getStorageSync('lifeData'); +}catch(e){ + +} + +// 需要永久存储,且下次APP启动需要取出的,在state中的变量名 +let saveStateKeys = ['vuex_user', 'vuex_token']; + +// 保存变量到本地存储中 +const saveLifeData = function(key, value){ + // 判断变量名是否在需要存储的数组中 + if(saveStateKeys.indexOf(key) != -1) { + // 获取本地存储的lifeData对象,将变量添加到对象中 + let tmp = uni.getStorageSync('lifeData'); + // 第一次打开APP,不存在lifeData变量,故放一个{}空对象 + tmp = tmp ? tmp : {}; + tmp[key] = value; + // 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中 + uni.setStorageSync('lifeData', tmp); + } +} +const store = new Vuex.Store({ + // 下面这些值仅为示例,使用过程中请删除 + state: { + // 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量 + // 加上vuex_前缀,是防止变量名冲突,也让人一目了然 + vuex_user: lifeData.vuex_user ? lifeData.vuex_user : {name: '明月'}, + vuex_token: lifeData.vuex_token ? lifeData.vuex_token : '', + // 如果vuex_version无需保存到本地永久存储,无需lifeData.vuex_version方式 + vuex_version: '1.0.1', + }, + mutations: { + $uStore(state, payload) { + // 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1 + let nameArr = payload.name.split('.'); + let saveKey = ''; + let len = nameArr.length; + if(nameArr.length >= 2) { + let obj = state[nameArr[0]]; + for(let i = 1; i < len - 1; i ++) { + obj = obj[nameArr[i]]; + } + obj[nameArr[len - 1]] = payload.value; + saveKey = nameArr[0]; + } else { + // 单层级变量,在state就是一个普通变量的情况 + state[payload.name] = payload.value; + saveKey = payload.name; + } + // 保存变量到本地,见顶部函数定义 + saveLifeData(saveKey, state[saveKey]) + } + } +}) + +export default store \ No newline at end of file diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..6fe4b58 --- /dev/null +++ b/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ +@import '@/uni_modules/uview-ui/theme.scss'; +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; diff --git a/utils/common.js b/utils/common.js new file mode 100644 index 0000000..4d901c1 --- /dev/null +++ b/utils/common.js @@ -0,0 +1,88 @@ +//获取当前时间,格式YYYY-MM-DD HH:MM:SS +const GetNowTime = time => { + var date = time, + year = date.getFullYear(), + month = date.getMonth() + 1, + day = date.getDate(), + hour = date.getHours() < 10 ? "0" + date.getHours() : date.getHours(), + minute = date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes(), + second = date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds(); + month >= 1 && month <= 9 ? (month = "0" + month) : ""; + day >= 0 && day <= 9 ? (day = "0" + day) : ""; + // var timer = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second; + var timer = year + '-' + month + '-' + day; + return timer; +} + +// 格式化电话号码 +const GetPhone = phone => { + let tel = phone.slice(0, 3) + '****' + phone.slice(7, 11); + return tel; +} +//返回日期和周几数组 +function weekDate() { + const myDate = new Date(); + const padZero = num => String(num).padStart(2, '0'); // 补零工具函数 + + const dayList = []; + + // 生成当天数据(补零) + dayList.push({ + day: padZero(myDate.getDate()), + month: padZero(myDate.getMonth() + 1), + week: toWeekDay(myDate.getDay()), + year: myDate.getFullYear() + }); + + // 生成后续10天数据 + for (let i = 0; i < 10; i++) { + myDate.setDate(myDate.getDate() + 1); + dayList.push({ + day: padZero(myDate.getDate()), + month: padZero(myDate.getMonth() + 1), + week: toWeekDay(myDate.getDay()), + year: myDate.getFullYear() + }); + } + + // 返回标准化日期格式 + return { + dayList, + StartDate: `${dayList[0].year}-${dayList[0].month}-${dayList[0].day}`, + EndDate: `${dayList[dayList.length - 1].year}-${dayList[dayList.length - 1].month}-${dayList[dayList.length - 1].day}` + }; +} + +function toWeekDay(weekDay) { // 传入数据 讲一周的某一天返回成中文状态下的字符 + switch (weekDay) { + case 1: + return '一'; + break; + case 2: + return '二'; + break; + case 3: + return '三'; + break; + case 4: + return '四'; + break; + case 5: + return '五'; + break; + case 6: + return '六'; + break; + case 0: + return '日'; + break; + default: + break; + } + return '传入未知参数'; +} +module.exports = { + GetNowTime, + GetPhone, + weekDate +} \ No newline at end of file diff --git a/utils/interceptor.js b/utils/interceptor.js new file mode 100644 index 0000000..0e6ce41 --- /dev/null +++ b/utils/interceptor.js @@ -0,0 +1,50 @@ + +import systemConfig from '@/config/config.js'; +// 页面白名单,不受拦截 +const whiteList = systemConfig.whiteList +function hasPermission (url) { + let islogin = uni.getStorageSync(systemConfig.token );//isLogin是登录成功后在本地存储登录标识,存储一个能够判断用户登录的唯一标识就行 + // 在白名单中或有登录判断条件可以直接跳转 + if(whiteList.indexOf(url) !== -1 || islogin) { + //console.log('通过') + return true + } + //console.log('失败') + return false +} +uni.addInterceptor('navigateTo', { + // 页面跳转前进行拦截, invoke根据返回值进行判断是否继续执行跳转 + invoke (e) { + const url = e.url.split('?')[0] + if(!hasPermission(url)){ + uni.reLaunch({ + url:systemConfig.login_page + }) + return false + } + return true + }, + success (e) { + + } +}) + +uni.addInterceptor('switchTab', { + // tabbar页面跳转前进行拦截 + invoke (e) { + + const url = e.url.split('?')[0] + if(!hasPermission(url)){ + uni.reLaunch({ + url: systemConfig.login_page + }) + //console.log('不在白名单内') + return false + } + //console.log('在白名单内') + return true + }, + success (e) { + + } +}) \ No newline at end of file diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..4851b88 --- /dev/null +++ b/utils/request.js @@ -0,0 +1,62 @@ + + +import BASE_URL from '@/api/env.js' //引入接口共用地址 +import RequestManager from '@/utils/requestManager.js' +import {toast, clearStorageSync, getStorageSync, useRouter} from '@/utils/utils.js' +import systemConfig from '@/config/config.js'; +const manager = new RequestManager() + +const baseRequest = async (url, method, data = {}, loading = true) =>{ + let requestId = manager.generateId(method, url, data) + if(!requestId) { + console.log('重复请求') + } + if(!requestId)return false; + + const header = {} + header.Authorization = getStorageSync(systemConfig.token) || '' + return new Promise((reslove, reject) => { + loading && uni.showLoading({title: 'loading'}) + uni.request({ + url: BASE_URL + url, + method: method || 'GET', + header: header, + timeout: 10000, + data: data || {}, + complete: ()=>{ + uni.hideLoading() + manager.deleteById(requestId) + }, + success: (successData) => { + //console.log(successData) + const res = successData.data + if(successData.statusCode == 200){ + // 业务逻辑,自行修改,401是服务器上返回该token过期,过期后跳转到登陆页面 + if(res.code > 400){ + clearStorageSync('token') + useRouter(systemConfig.login_page, 'reLaunch') + }else{ + reslove(res) + } + }else{ + console.log('网络连接失败,请稍后重试' ,url) + toast('网络连接失败,请稍后重试') + reject(res) + } + }, + fail: (msg) => { + console.log("请求:"+BASE_URL + url +',发生错误:', err) + toast('网络连接失败,请稍后重试') + reject(msg) + } + }) + }) +} + +const request = {}; + +['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => { + request[method] = (api, data, loading) => baseRequest(api, method, data, loading) +}) + +export default request \ No newline at end of file diff --git a/utils/requestManager.js b/utils/requestManager.js new file mode 100644 index 0000000..56dff20 --- /dev/null +++ b/utils/requestManager.js @@ -0,0 +1,66 @@ +class RequestManager { + constructor() { + this.idMap = new Map() + } + /** + * 生成唯一ID,并将ID和请求信息存储到map对象中 + * @param {string} method - 请求方法 + * @param {string} url - 请求URL + * @param {object} params - 请求参数 + * @returns {string|boolean} - 生成的唯一ID,如果存在相同请求则返回false + */ + generateId(method, url, params) { + const id = this.generateUniqueId(method, url, params) + if (this.idMap.has(id)) { + return false + } + this.idMap.set(id, { method, url, params }) + return id + } + + /** + * 根据ID删除map对象中的请求信息 + * @param {string} id - 要删除的唯一ID + */ + deleteById(id) { + this.idMap.delete(id) + } + + /** + * 生成唯一ID的方法 + * @param {string} method - 请求方法 + * @param {string} url - 请求URL + * @param {object} params - 请求参数 + * @returns {string} - 生成的唯一ID + */ + generateUniqueId(method, url, params) { + const idString = `${method}-${url}-${this.serializeObject(params)}` + let id = 0; + for (let i = 0; i < idString.length; i++) { + id = ((id << 5) - id) + idString.charCodeAt(i) + id |= 0; + } + return id.toString() + } + + /** + * 序列化对象为字符串 + * @param {object} obj - 要序列化的对象 + * @returns {string} - 序列化后的字符串 + */ + serializeObject(obj) { + const keys = Object.keys(obj).sort() + const serializedObj = {} + for (let key of keys) { + const value = obj[key] + if (value !== null && typeof value === 'object') { + serializedObj[key] = this.serializeObject(value) + } else { + serializedObj[key] = value + } + } + return JSON.stringify(serializedObj) + } +} + +export default RequestManager \ No newline at end of file diff --git a/utils/utils.js b/utils/utils.js new file mode 100644 index 0000000..0ff3976 --- /dev/null +++ b/utils/utils.js @@ -0,0 +1,475 @@ +/** + * 提示方法 + * @param {String} title 提示文字 + * @param {String} icon icon图片 + * @param {Number} duration 提示时间 + */ +export function toast(title, icon = 'none', duration = 1500) { + if(title) { + uni.showToast({ + title, + icon, + duration + }) + } +} + +/** + * @param {String} url + * @return {function} + * @description navigateTo跳转 + */ +export function Jump(url) { + uni.navigateTo({ + url: url + }) +} + + + +/** + * 提示信息 + * **/ +export function showModal(title, msg, showCancel) { + uni.showModal({ + title: title, + content: msg, + showCancel: showCancel, + success: function(res) { + + } + }) +} + + +export function showLoading(content) { + uni.showLoading({ + title: content + }) +} +/** + * 隐藏loading + * **/ +export function hideLoading() { + uni.hideLoading() +} + +/*** + * 获取时间戳 + */ +export function getTimStamp() { + var timestamp = Date.parse(new Date()); + timestamp = timestamp / 1000; + return timestamp; +} +/*** + * 生成随机数 + */ +export function getNonce() { + var t = ''; + for (var i = 0; i < 12; i++) { + t += Math.floor(Math.random() * 10); + } + return t; +} +/** + * 获取32位随机数 + * ***/ +export function getnoncestr() { + let len = len || 32; + var $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + var maxPos = $chars.length; + var pwd = ''; + for (var i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; +} + +/** + * 解析url中的参数 + */ +export function UrlParamHash(url) { + var params = {}, + h; + var hash = url.slice(url.indexOf("?") + 1).split("&"); + for (var i = 0; i < hash.length; i++) { + h = hash[i].split("="); + if (h.length > 1) { + params[h[0]] = h[1]; + } else { + params[h[0]] = ""; + } + } + return params; +} + + +/** + * 一键复制 uni.setClipboardData + * 用法: + * 1. import {setClipboardData} from '该文件的地址' + * 2. 利用async await + * 3. await setClipboardData(需要复制的文本) + */ +const setClipboardData = (text) =>{ + return new Promise((resolve,reject)=>{ + uni.setClipboardData({ + data: text, + success: (res) => { + resolve(res) + }, + fail: (err) => { + reject(err) + } + }) + }) +} + +/** + * 获取系统剪贴板内容 uni.getClipboardData + * 用法: + * 1. import {getClipboardData} from '该文件的地址' + * 2. 利用async await来接收获取到的数据 + * 3. await getClipboardData() + */ +const getClipboardData = () =>{ + return new Promise((resolve,reject)=>{ + uni.getClipboardData({ + success: (res) => { + resolve(res) + }, + fail: (err) => { + reject(err) + } + }) + }) +} + +/** + * 拨打电话 uni.makePhoneCall() + * 用法: + * 1. import {getBatteryInfo} from '该文件的地址' + * 2. 直接makePhoneCall(拨打的电话号码) + */ +const makePhoneCall = (phone) =>{ + uni.makePhoneCall({ + phoneNumber:phone + }) +} + + +/** + * 设置缓存 + * @param {String} key 键名 + * @param {String} data 值 + */ +export function setStorageSync(key, data) { + uni.setStorageSync(key, data) +} + +/** + * 获取缓存 + * @param {String} key 键名 + */ +export function getStorageSync(key) { + return uni.getStorageSync(key) +} + +/** + * 删除缓存 + * @param {String} key 键名 + */ +export function removeStorageSync(key) { + return uni.removeStorageSync(key) +} + +/** + * 清空缓存 + * @param {String} key 键名 + */ +export function clearStorageSync() { + return uni.clearStorageSync() +} + + +/** + * 页面跳转 + * @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url 转跳路径 + * @param {String} params 跳转时携带的参数 + * @param {String} type 转跳方式 + **/ +export function useRouter(url, params = {}, type = 'navigateTo') { + try { + if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}` + if (type === 'navigateBack') { + uni[type]({ delta: url }) + } else { + uni[type]({ url }) + } + } catch (error) { + console.error(error) + } +} + +/** + * 预览图片 + * @param {Array} urls 图片链接 + */ +export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) { + uni.previewImage({ + urls, + longPressActions: { + itemList, + fail: function (error) { + console.error(error,'===previewImage') + } + } + }) +} + +/** + * 保存图片到本地 + * @param {String} filePath 图片临时路径 + **/ +export function saveImage(filePath) { + if (!filePath) return false + uni.saveImageToPhotosAlbum({ + filePath, + success: (res) => { + toast('图片保存成功', 'success') + }, + fail: (err) => { + if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') { + uni.showModal({ + title: '提示', + content: '需要您授权保存相册', + showCancel: false, + success: (modalSuccess) => { + uni.openSetting({ + success(settingdata) { + if (settingdata.authSetting['scope.writePhotosAlbum']) { + uni.showModal({ + title: '提示', + content: '获取权限成功,再次点击图片即可保存', + showCancel: false + }) + } else { + uni.showModal({ + title: '提示', + content: '获取权限失败,将无法保存到相册哦~', + showCancel: false + }) + } + }, + fail(failData) { + console.log('failData', failData) + } + }) + } + }) + } + } + }) +} + +/** + * 深拷贝 + * @param {Object} data + **/ +export const clone = (data) => JSON.parse(JSON.stringify(data)) + /** + * 获取平台名称 + * @return {string} 平台名称 + */ +export function getPlatform() { + let platform; + switch (process.env.VUE_APP_PLATFORM) { + case 'app': + case 'app-plus': + let n = uni.getSystemInfoSync().platform.toLowerCase(); + if (n === 'ios') { + platform = 'ios'; + } else if (n === 'android') { + platform = 'android'; + } else { + platform = 'app'; + } + break; + case 'mp-weixin': + platform = 'wx'; + break; + case 'mp-alipay': + platform = 'alipay'; + break; + case 'mp-baidu': + platform = 'baidu'; + break; + case 'mp-qq': + platform = 'qq'; + break; + case 'mp-toutiao': + platform = 'toutiao'; + break; + case 'quickapp-webview': + platform = 'kuai'; + break; + } + + return platform; +} + +/** + * 数组去重 + * @param {Array} array 数值 + * @retrun {Array} 数值 + */ +export function arrayShuffle(array) { + let i = array.length, t, j; + while (i) { + j = Math.floor(Math.random() * i--); + t = array[i]; + array[i] = array[j]; + array[j] = t; + } + return array; +} + +/** + * 日期格式化 + * @param {Date} date 日期 + * @param {string} format 返回的日期格式 + * @retrun {string} 日期 + */ +export function dateFormat(date, format = 'YYYY-MM-DD HH:mm:ss') { + const config = { + YYYY: date.getFullYear(), + MM: date.getMonth()+1, + DD: date.getDate(), + HH: date.getHours(), + mm: date.getMinutes(), + ss: date.getSeconds(), + } + for(const key in config){ + let value = config[key]; + if (value < 10) { + value = '0' + value; + } + format = format.replace(key, value) + } + return format +} + +/** + * base64转文件 + * @param {string} base64data base64 + * @param {Function} cb 回调 + */ +export function base64ToSrc(base64data, cb) { + const FILE_BASE_NAME = 'tmp_base64src'; + const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || []; + if (!format) { + return (new Error('格式错误')); + } + + // #ifdef MP-WEIXIN + let filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`; + // #endif + // #ifdef MP-QQ + let filePath = `${qq.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`; + // #endif + + const buffer = uni.base64ToArrayBuffer(bodyData); + uni.getFileSystemManager().writeFile({ + filePath, + data: buffer, + encoding: 'binary', + success() { + cb && cb(filePath); + } + }); +} + +/** + * base64解码 + * @param {string} str str + * @param {string} + */ +export function encodeBase64(str) { + return new Buffer.from(str).toString('base64'); +} + +/** + * base64解码 + * @param {string} str str + * @param {string} + */ +export function decodeBase64(str) { + const commonContent = str.replace(/\s/g, '+'); + return new Buffer.from(commonContent, 'base64').toString(); +} + +/** + * 播放声音 + * @param {string} src 声音文件地址 + * @param {Boolean} loop 是否循环 + */ +export function playSound(src, loop = false) { + const innerAudioContext = uni.createInnerAudioContext(); + innerAudioContext.autoplay = true; + innerAudioContext.loop = loop; + innerAudioContext.src = src; + innerAudioContext.onPlay(() => {}); + innerAudioContext.onError((res) => {}); +} + +/** + * 生成订单ID + * @param {string} prefix 订单前缀 + * @param {string} 订单ID + */ +export function createOrderSn(prefix = '') { + return `${prefix}${this.randomString(10).toUpperCase()}${+new Date()}`; +} + +/** + * 图片转base64 + * @param {string} src 图片地址 + * @return {Promise} base64 + */ +export function imageToBase64(src) { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src, + success: image => { + console.log(image); + uni.getFileSystemManager().readFile({ + filePath: image.path, + encoding: 'base64', + success: e => { + return resolve(`data:image/jpeg;base64,${e.data}`); + }, + fail: e => { + return reject(null); + } + }) + } + }); + }) +} + + +/** + * 随机范围内的数字 + * @param {number} start 起始数字 + * @param {number} end 起始数字 + * @return {number || null} 随机数 + */ +export function randomByRange(start, end){ + if (typeof start === 'number' && typeof end === 'number') { + return start + Math.floor(Math.random() * (end - start)); + } else { + console.error('参数必须为数字'); + return null; + } +} \ No newline at end of file