This commit is contained in:
陆亚狮 2025-04-07 09:30:11 +08:00
commit 53af2dd84e
62 changed files with 5336 additions and 0 deletions

63
App.vue Normal file
View File

@ -0,0 +1,63 @@
<script>
// #ifdef APP-PLUS
import appUpdate from '@/components/appUpdate/appUpdate.js'
// #endif
export default {
onLaunch: function() {
//console.log('App Launch')
// #ifdef APP-PLUS
let systemInfo = uni.getSystemInfoSync()
const data={
appid: systemInfo.appId,
version: systemInfo.appVersion
}
console.log(data)
//appid
this.$api.checkVersion(data).then(res => {
console.log(res)
if(res.code==0){
//0
appUpdate(res)
}
})
// #endif
},
onShow: function() {
// #ifdef MP-WEIXIN
// res: {hasUpdate: true, version: 1.0.0}
updateManager.onCheckForUpdate(function (res) {
if (res.hasUpdate) { //
uni.showLoading({title:'更新中...'}); // Loading
}
});
//
updateManager.onUpdateReady(function () {
uni.hideLoading(); // Loading
uni.showModal({ //
title:'更新提示',
content:'更新完毕,是否重启?',
success:function (res) {
if (res.confirm) {
updateManager.applyUpdate(); // 使
}
}
})
});
//
updateManager.onUpdateFailed(function () {
uni.hideLoading(); // Loading
uni.showToast({ title:'更新失败,稍后再试...', icon:"error" });
});
// #endif
},
onHide: function() {
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uview-ui/index.scss";
</style>

37
api/api.js Normal file
View File

@ -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)

11
api/env.js Normal file
View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,117 @@
<template>
<view>
<view class="box">
<view class="title">{{ currentYear }}&nbsp;&nbsp;{{ currentMonth }}</view>
<scroll-view scroll-x="true">
<block v-for="(item, index) in dayList" :key="index">
<view
class="dayTitle"
:class="current == index ? 'select' : ''"
@click="timeSelectd(index)"
>
<view
style="
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
"
>
<view class="day">{{ item.day }}</view>
<view v-if="index == 0" style="font-size: 25rpx">今天</view>
<view v-else style="font-size: 25rpx">{{ item.week }}</view>
</view>
</view>
</block>
</scroll-view>
</view>
</view>
</template>
<script>
import Vue from "vue";
import common from "@/utils/common.js";
export default {
data() {
return {
currentYear: new Date().getFullYear(), //
currentMonth: new Date().getMonth() + 1, // 0-11+1
isShow: false,
current: 0,
dayList: [],
xzTime: common.GetNowTime(new Date()),
};
},
created() {
this.dayList = common.weekDate().dayList;
const firstDay = this.dayList[0];
this.currentYear = firstDay.year;
this.currentMonth = firstDay.month; // dayList
},
methods: {
//
timeSelectd(index) {
this.current = index;
let date =
this.dayList[index].year +
"-" +
this.dayList[index].month +
"-" +
this.dayList[index].day;
date = common.GetNowTime(new Date(date));
this.xzTime = date;
},
},
};
</script>
<style scoped lang="scss">
.box {
padding: 30rpx;
}
scroll-view {
padding: 20rpx 0;
width: 100%;
white-space: nowrap;
}
.title {
font-weight: 800;
font-size: 70rpx;
color: #282828;
}
.dayTitle {
margin-left: 20rpx;
text-align: center;
display: inline-block;
width: 124rpx;
height: 124rpx;
background: #ffffff;
border-radius: 25rpx;
border: 1rpx solid #4689fe;
.day {
font-weight: normal;
font-size: 34rpx;
color: #4473fe;
}
}
.dayTitle:first-child {
margin-left: 0;
}
.select {
font-weight: bold;
font-size: 34rpx;
color: #ffffff;
background: #4473fe;
border-radius: 25rpx;
border: 1rpx solid #4473fe;
.day {
color: #ffffff;
}
}
</style>

17
config/config.js Normal file
View File

@ -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

20
index.html Normal file
View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

47
main.js Normal file
View File

@ -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

91
manifest.json Normal file
View File

@ -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" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PRIVILEGED\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* 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"
}

20
package.json Normal file
View File

@ -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前端模板",
"前端页面模板"
]
}
}

222
pages.json Normal file
View File

@ -0,0 +1,222 @@
{
// uni_modulesuView
"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/indexpages/subPack
"pages/index/index": {
"network": "all", //allwifiwifi
"packages": ["pages/subPack"] //
}
},
"uniIdRouter": {}
}

155
pages/detail.vue Normal file
View File

@ -0,0 +1,155 @@
<template>
<view class="content">
<view class="custom-card">
<view class="card-header">
<view class="header-left">
<u-tag :text="item.goods_type" type="success" shape="circleRight"/>
<view class="header-title"></view>
</view>
<view class="header-right">
<u-tag text="待服务" shape="circleLeft" type="primary" v-if="item.type==1"/>
<u-tag text="服务中" shape="circleLeft" type="info" v-if="item.type==2"/>
<u-tag text="待验收" shape="circleLeft" type="warning" v-if="item.type==3"/>
<u-tag text="问题单" shape="circleLeft" type="error" v-if="item.type==4"/>
<u-tag text="已完成" shape="circleLeft" type="success" v-if="item.type==5"/>
</view>
</view>
<view class="card-body" @click="detail(item.id)">
<u-alert-tips type="warning" :description="item.remark" v-if="item.remark"></u-alert-tips>
<view class="tags">
<u-tag text="加急" type="error" mode="dark" v-if="item.urgent==1"/>
<u-tag text="设备在家" type="primary" mode="light" v-if="item.have_device==1"/>
<u-tag text="设备不在家" type="primary" mode="light" v-if="item.have_device==0"/>
<u-tag text="收费" type="warning" mode="light" v-if="item.charge==1"/>
<u-tag text="不收费" type="warning" mode="light" v-if="item.charge==0"/>
<u-tag text="签约单" type="success" mode="light" v-if="item.sign_order==1"/>
</view>
<view class="section-title">基本信息</view>
<view class="card-body-item">
<view class="item-title">订单编号</view>
<view class="item-content">{{item.order_sn}}</view>
</view>
<view class="section-title">派单信息</view>
<view class="card-body-item">
<view class="item-title">派单时间</view>
<view class="item-content">{{item.dispatch_time}}</view>
</view>
<view class="card-body-item">
<view class="item-title">安装师傅</view>
<view class="item-content">{{item.master_name}}</view>
</view>
<view class="card-body-item">
<view class="item-title">师傅电话</view>
<view class="item-content">{{item.master_tel}}</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
item : []
}
},
onLoad(op) {
this.id = op.id
this.getDetail()
},
onShow() {
},
onReady() {
},
methods: {
getDetail(){
const data = {
advance_id : this.id
}
this.$api.advanceDetail(data).then(res => {
if (res.code == 0) {
this.item = res.data
} else {
}
})
},
}
}
</script>
<style lang="scss">
page{background: #f2f2f2;}
.content{padding: 20rpx;}
.custom-card {
background-color: #fff;
border: 1px solid #f2f2f2;
border-radius: 4px;
padding: 8px 0;
margin-top: 15rpx;
.card-header {
border-bottom: 1px solid #f2f2f2;
padding-bottom: 16rpx;
display: flex;
justify-content: space-between;
.header-left{
display: flex;
.header-title{
color:#333;
font-weight: bold;
margin-top: 3rpx;
margin-left: 10rpx;
}
}
.header-right{
.date{font-size: 26rpx;color:#999;font-weight: normal;margin-right: 15rpx;margin-top: 5rpx;}
}
}
.card-body{
padding: 20rpx;
.section-title{font-weight: bold; margin-top: 20rpx;}
.card-body-item{
display: flex;
line-height: 80rpx;
border-bottom: 1px solid #efefef;
.item-title{color:#A9A9A9}
.item-content{
flex:1;
color:#666;
.goods_list{display: flex;line-height: 60rpx;
.install{width: 60%;}
.price{width: 20%;}
.master_money{width: 20%;}
}
.photo_list{ display: flex;}
.photo_list image{width:80rpx;height:100rpx; margin-top: 10rpx; margin-left: 10rpx;}
}
.item-right{margin-right: 8rpx;}
}
.tags{
margin-top: 10rpx;
line-height:60rpx;
.u-tag{margin-right: 15rpx;}
}
.fee{
background: #F9F9F9;
padding:20rpx;
display: flex;
.fee-item{width: 50%;text-align: center;
.fee-item-title{
color: #A9A9A9;
}
}
}
}
.card-footer {
display: flex;
}
}
</style>

205
pages/form.vue Normal file
View File

@ -0,0 +1,205 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm">
<u-form-item label="标识号:" prop="login_code" label-width="170">
<u-input placeholder="请输入标识号,若不知,请联系所属公司" v-model="form.login_code" />
</u-form-item>
<u-form-item label="真实姓名:" label-width="170" prop="name">
<u-input placeholder="请输入真实姓名" v-model="form.name" />
</u-form-item>
<u-form-item label="手机号:" label-width="170" prop="phone">
<u-input placeholder="请输入输入手机号码" v-model="form.phone" />
</u-form-item>
<u-form-item label="登陆密码:" label-width="170" prop="password">
<u-input placeholder="请输入登陆密码" v-model="form.password" />
</u-form-item>
<u-form-item label="身份证号:" label-width="170" prop="cardno">
<u-input placeholder="请输入身份证号码" v-model="form.cardno" />
</u-form-item>
<u-form-item label="身份证正反面:" label-width="190">
<u-upload upload-text="选择或拍照" width="180" height="180" ref="uUpload" :action="action" :fileList="fileList" :form-data="form_data" ></u-upload>
</u-form-item>
<view class="agreement">
<u-checkbox size="40rpx" v-model="check" @change="checkboxChange"></u-checkbox>
<view class="agreement-text">
我已知晓并同意 <text @click="showPopup(4)" style="color:cornflowerblue">用户服务协议</text><text @click="showPopup(5)" style="color:cornflowerblue">隐私政策</text>
</view>
</view>
<view class="submit_con">
<u-button type="primary" @click="submit">提交注册</u-button>
</view>
</u-form>
<u-popup v-model="popupVisible" mode="bottom" length="60%">
<view class="popup-content">
<rich-text :nodes="protocolsContent" style="line-height: 50rpx;"></rich-text>
</view>
<view class="popup-footer">
<u-button class="close_pop" size="medium" type="primary" @click="hidePopup">关闭</u-button>
</view>
</u-popup>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync,getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
action: this.$api_url + '/api/index/upload_cos',
fileList:[],
popupVisible: false,
protocolsContent: "",
check: false,
agreement: false,
form: {
login_code: '',
name: '',
phone: '',
cardno: '',
photo:'',
password:''
},
form_data : {
floder : 'photo'
},
rules: {
login_code: [
{
required: true,
type:"number",
message: '请输入标识号',
//
trigger: ['change','blur'],
}
],
password: [
{
required: true,
message: '请输入密码',
//
trigger: ['change','blur'],
}
],
name: [
{
required: true,
message: '请输入真实姓名',
//
trigger: ['change','blur'],
}
],
phone: [
{
required: true,
type:"number",
message: '请输入登陆手机号',
//
trigger: ['change','blur'],
}
],
cardno: [
{
required: true,
message: '请输入身份证号码',
//
trigger: ['change','blur'],
}
]
}
};
},
onReady(){
this.$refs.uForm.setRules(this.rules);
},
methods: {
checkboxChange(e) {
this.agreement = e.value;
},
showPopup(id) {
this.popupVisible = true;
const params ={id:id}
this.$api.page(params).then(res => {
this.protocolsContent = res.data.content
})
},
hidePopup() {
this.popupVisible = false;
},
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
if(!this.agreement) return toast('请查看并同意相关协议');
//
let _uploadPhoto_data = {}
var _list = this.$refs.uUpload.lists
for (let i = 0; i < _list.length; i++) {
_uploadPhoto_data[i] = {
url: _list[i].response.data.file
}
}
if(Object.keys(_uploadPhoto_data).length ==0 ){
toast('必须上传身份证正反面')
return
}
this.form.photo = JSON.stringify(_uploadPhoto_data)
this.$api.register(this.form).then(res => {
if(res.code==0){
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},1500)
console.log(res);
}else{
toast(res.msg)
console.log(res);
}
})
} else {
console.log('验证失败');
}
})
}
},
}
</script>
<style lang="scss">
.popup-content {
padding: 40rpx;
padding-bottom: 120rpx;
}
.popup-footer {
margin-top: 20rpx;
text-align: center;
position: fixed;
bottom: 0;
width: 100%;
}
.content{padding:30rpx}
.agreement {
display: flex;
margin: 40rpx 0 0 10rpx;
.agreement-text {
padding-left: 0rpx;
color: $u-tips-color;
}
}
.submit_con{margin-top: 20rpx;}
</style>

View File

@ -0,0 +1,116 @@
<template>
<view class="content">
<u-navbar title="保洁详情"></u-navbar>
<view class="area-info">
<view class="name">保洁区域名称</view>
<view class="position">所在区域学校操场</view>
</view>
<view class="area-details">
<view class="title">区域详情</view>
<view class="details">
<view class="block">
<view class="value">室内/室外</view>
<view class="label">区域类型</view>
</view>
<view class="block">
<view class="value">保洁员1号</view>
<view class="label">保洁人员</view>
</view>
<view class="block">
<view class="value">2024/1/27 9:53:35</view>
<view class="label">清扫时间</view>
</view>
<view class="block">
<view class="value">清扫完成</view>
<view class="label">备注</view>
</view>
</view>
<view class="title">保洁区域图片</view>
<view class="images">
<u-image
class="image"
v-for="(v, i) in 7"
:key="i"
width="212rpx"
height="212rpx"
src="https://cdn.uviewui.com/uview/example/fade.jpg"
></u-image>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
};
</script>
<style lang="scss" scoped>
.content {
height: 100vh;
background: linear-gradient(#fff 0%, #e0ebf9 20%);
.title {
line-height: 1;
border-left: 10rpx solid #5a7ee9;
padding-left: 16rpx;
font-weight: bold;
font-size: 36rpx;
color: #000000;
}
.area-info {
padding: 44rpx;
margin-top: 30rpx;
height: 290rpx;
background: url("@/static/images/area-bg.png") no-repeat 100% 100%;
background-size: 100% 100%;
display: flex;
flex-direction: column;
.name {
font-weight: bold;
font-size: 56rpx;
color: #211d2f;
}
.position {
margin-top: 44rpx;
font-weight: 500;
font-size: 32rpx;
color: #717b94;
}
}
.area-details {
padding: 50rpx 32rpx;
background: #ffffff;
border-radius: 36rpx 36rpx 0 0;
min-height: calc(100vh - 400rpx);
.details {
margin-top: 42rpx;
margin-bottom: 70rpx;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.block {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 48.5%;
height: 160rpx;
background: #f1f7fe;
border-radius: 18rpx;
}
.block:nth-child(n + 3) {
margin-top: 14rpx;
}
}
.images {
margin-top: 46rpx;
display: grid;
grid-template-columns: repeat(auto-fill, 212rpx);
gap: 20rpx; //
}
}
}
</style>

224
pages/index/cleanPlan.vue Normal file
View File

@ -0,0 +1,224 @@
<template>
<view class="content">
<u-navbar
:is-back="false"
title=""
:background="{ backgroundColor: '#DFEEFD' }"
:border-bottom="false"
>
<view class="slot-wrap">
<u-dropdown :title-size="42" active-color="#211D2F">
<u-dropdown-item
v-model="dropdownValue"
title="保洁计划"
:options="dropdownOptions"
></u-dropdown-item>
</u-dropdown>
</view>
</u-navbar>
<view class="area-info">
<view class="name">2024年11月21号</view>
<view class="position"> 0830~1130</view>
<view class="tabs">
<view
@click="changeTab(i)"
class="tab-item"
v-for="(v, i) in selectTabs"
:key="i"
:class="{ select: selectIdx === i }"
>
{{ v.name }}
</view>
</view>
</view>
<view class="arealist">
<view class="block-info">
<view class="schedule">待保洁</view>
<view class="name">
<u-icon size="40" :name="posIcon"></u-icon>
<view class="text">东大门北侧_门2</view>
</view>
<view class="type">
<view class="value">区域类型</view>
<u-tag
text="室外"
class="tag"
border-color="transparent"
type="success"
/>
<u-tag text="特殊" border-color="transparent" type="warning" />
</view>
<view class="illustrate">
{{ "需要清理整个户外通道/保持跑道整洁花坛边落叶清扫" }}
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
posIcon: require("@/static/images/pos-icon.png"),
selectIdx: 0,
dropdownValue: 1,
dropdownOptions: [
{
label: "保洁计划1",
value: 1,
},
{
label: "保洁计划2",
value: 2,
},
{
label: "保洁计划3",
value: 3,
},
{
label: "保洁计划4",
value: 4,
},
],
selectTabs: [
{
name: "全部",
value: 0,
},
{
name: "待保洁",
value: 1,
},
],
};
},
created() {},
methods: {
changeTab(i) {
console.log(i);
this.selectIdx = i;
},
},
};
</script>
<style lang="scss" scoped>
.content {
height: 100vh;
background: #f7f8fc;
.slot-wrap {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
/deep/ .u-dropdown-item__options {
width: 100%;
}
}
.title {
line-height: 1;
border-left: 10rpx solid #5a7ee9;
padding-left: 16rpx;
font-weight: bold;
font-size: 36rpx;
color: #000000;
}
.area-info {
padding: 24rpx 32rpx;
padding-top: 72rpx;
display: flex;
flex-direction: column;
background: url("@/static/images/plan-icon-bg.png") no-repeat 90% 60%;
background-size: 40%;
background-color: #dfeefd;
.name {
font-weight: 800;
font-size: 48rpx;
color: #1d1d1d;
}
.position {
margin-top: 40rpx;
font-weight: 500;
font-size: 34rpx;
color: #465161;
}
.tabs {
margin-top: 38rpx;
display: flex;
justify-content: space-between;
.tab-item {
z-index: 99;
pointer-events: auto; /* 确保可点击 */
background: #ffffff;
border-radius: 30rpx;
padding: 18rpx 0;
width: 48%;
display: flex;
align-items: center;
justify-content: center;
color: #383535;
}
.select {
color: #ffffff;
background: #4278f4;
}
}
}
.arealist {
display: flex;
flex-direction: column;
padding: 32rpx;
.block-info {
position: relative;
background: #ffffff;
border-radius: 18rpx;
padding: 65rpx 46rpx;
.schedule {
position: absolute;
right: 0;
top: 0;
display: flex;
align-items: center;
justify-content: center;
background-color: #FEEDED; /* 背景颜色 */
color: white;
width: 160rpx;
height: 45rpx;
font-size: 30rpx;
color: #DB3636;
border-bottom-left-radius: 500rpx; /* 切角 */
}
.name {
display: flex;
align-items: center;
.text {
margin-left: 20rpx;
font-size: 38rpx;
color: #32353b;
}
}
.type {
display: flex;
margin: 38rpx 0;
.value {
margin-right: 10rpx;
font-size: 32rpx;
color: #8896b4;
}
.tag {
margin: 0 16rpx;
}
}
.illustrate {
padding: 46rpx;
background: #f3f6f7;
border-radius: 21rpx;
font-size: 32rpx;
color: #353535;
line-height: 57rpx;
}
}
}
}
</style>

173
pages/index/index.vue Normal file
View File

@ -0,0 +1,173 @@
<template>
<view class="content">
<daySelect />
<view class="task-info">
<view class="title">今日任务</view>
<view class="task-list">
<view class="task-item">
<view class="name-state">
<view class="name">足球场休息室</view>
<u-tag text="已完成" type="success" border-color="transparent" />
</view>
<view class="time">
<u-tag text="9:00~12:00" type="info" border-color="transparent" />
</view>
</view>
</view>
</view>
<view class="area-schedule">
<view class="finish">
<view class="point"></view>
<view class="title">已保洁区域</view>
<view class="count-rate">26 <view class="total">/50</view> </view>
<u-line-progress
height="10"
class="schedule-line"
:show-percent="false"
active-color="#2979ff"
:percent="70"
></u-line-progress>
</view>
<view class="unfinish">
<view class="point"></view>
<view class="title">已保洁区域</view>
<view class="count-rate">26 <view class="total">/50</view> </view>
<u-line-progress
height="10"
class="schedule-line"
:show-percent="false"
active-color="#FC873D"
:percent="70"
></u-line-progress>
</view>
</view>
<view class="cleaning-plan">
<view class="title">今日保洁计划名称</view>
<view class="time">
<view class="top">00:00:00</view>
<view class="bottom">23:59:00</view>
</view>
</view>
</view>
</template>
<script>
import daySelect from "@/components/daySelect/index.vue";
export default {
components: {
daySelect,
},
};
</script>
<style lang="scss" scoped>
.content {
height: 100vh; //
overflow: hidden;
}
.task-info {
margin: 0 32rpx;
padding: 18rpx;
background: #f5f6fa;
border-radius: 24rpx;
.title {
font-weight: bold;
font-size: 34rpx;
color: #282828;
}
.task-list {
margin-top: 36rpx;
display: flex;
flex-direction: column;
.task-item {
padding: 44rpx 30rpx;
background: #ffffff;
border-radius: 16rpx;
.name-state {
display: flex;
justify-content: space-between;
.name {
position: relative;
}
.name::before {
content: "";
position: absolute;
top: 10%;
left: -6%;
display: inline-block;
width: 5rpx;
height: 30rpx;
background: #3d6af0;
}
}
.time {
margin-top: 20rpx;
}
}
}
}
.area-schedule {
margin: 0 32rpx;
margin-top: 26rpx;
display: flex;
justify-content: space-between;
> view {
padding: 22rpx;
width: 48%;
background: #f5f6fa;
border-radius: 10rpx;
.point {
width: 12rpx;
height: 12rpx;
background: #4473fe;
border-radius: 50%;
}
.title {
margin-top: 28rpx;
font-size: 32rpx;
color: #292929;
}
.count-rate {
margin-left: 12rpx;
margin-top: 40rpx;
display: flex;
font-size: 50rpx;
color: #4473fe;
.total {
font-size: 50rpx;
color: #dbdee9;
}
}
.schedule-line {
margin-top: 32rpx;
}
}
.unfinish .point {
background: #fc873d;
}
.unfinish .count-rate {
color: #fc873d;
}
}
.cleaning-plan {
margin: 0 32rpx;
margin-top: 36rpx;
padding: 42rpx 52rpx;
display: flex;
justify-content: space-between;
align-items: center;
background: url("@/static/images/plan-bg.png") no-repeat 100% 100%;
background-size: 100% 100%;
.title {
font-weight: 500;
font-size: 36rpx;
color: #334a65;
}
.time{
font-weight: 500;
font-size: 36rpx;
color: #334A65;
}
}
</style>

170
pages/index/list.vue Normal file
View File

@ -0,0 +1,170 @@
<template>
<view class="content">
<view class="top">
<u-cell-group>
<u-cell-item :arrow="false" title="保洁人员" value="小饼"></u-cell-item>
<u-cell-item
:arrow="false"
title="上报时间"
value="2025.4.1"
></u-cell-item>
</u-cell-group>
</view>
<view class="title">保洁区域</view>
<view class="bottom">
<u-form :model="form" :rules="rules" ref="uForm" label-width="200">
<!-- 区域选择多选 -->
<u-form-item label="东大门操场" prop="areas" required>
<u-input v-model="form.cleaner" placeholder="请输入故障" />
</u-form-item>
<!-- 备注文本域 -->
<u-form-item label="备注" prop="notes" label-position="top">
<u-input
v-model="form.notes"
placeholder="请输入备注信息"
autoHeight
maxlength="200"
count
type="textarea"
></u-input>
</u-form-item>
<!-- 图片上传 -->
<u-form-item
label="现场照片"
prop="images"
required
label-position="top"
>
<u-upload
v-model="form.images"
:max-count="3"
:previewFullImage="true"
:multiple="true"
@afterRead="handleImageUpload"
@delete="handleImageDelete"
></u-upload>
</u-form-item>
</u-form>
</view>
<!-- 提交按钮 -->
<u-button @click="submitForm" customStyle="margin-top: 40rpx" type="primary"
>保存</u-button
>
</view>
</template>
<script>
export default {
data() {
return {
form: {
cleaner: "", //
reportTime: "", //
areas: [], //
notes: "", //
images: [], //
},
areaOptions: [
{ label: "东大门操场", value: "east_gate" },
{ label: "西区教学楼", value: "west_building" },
{ label: "中心花园", value: "central_garden" },
],
rules: {
cleaner: [
{
required: true,
message: "请填写保洁人员",
trigger: ["blur", "change"],
},
],
reportTime: [
{
required: true,
message: "请选择上报时间",
trigger: ["change"],
},
],
areas: [
{
type: "array",
required: true,
message: "请至少选择一个清洁区域",
trigger: ["change"],
},
],
images: [
{
type: "array",
min: 1,
required: true,
message: "请至少上传一张现场照片",
trigger: ["change"],
},
],
},
};
},
methods: {
//
handleImageUpload(event) {
const files = event.file;
//
this.form.images.push(...files.map((file) => ({ url: file.url })));
},
//
handleImageDelete(index) {
this.form.images.splice(index, 1);
},
//
submitForm() {
this.$refs.uForm.validate().then((valid) => {
if (valid) {
uni.showToast({
title: "提交成功",
icon: "success",
});
//
}
});
},
},
};
</script>
<style lang="scss" scoped>
.content {
height: auto;
min-height: 100vh;
padding: 24rpx 32rpx;
background-color: #f3f5fa;
.bottom,
.top {
background: #ffffff;
border-radius: 22rpx;
padding: 0rpx 32rpx;
margin-bottom: 30rpx;
}
.title {
font-size: 38rpx;
color: #1a1a1b;
margin: 42rpx 0 32rpx 0;
font-weight: bold;
}
//
.u-checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 30rpx;
}
}
</style>

90
pages/index/planList.vue Normal file
View File

@ -0,0 +1,90 @@
<template>
<view class="content">
<u-navbar
:is-back="false"
title="计划列表"
:background="{ backgroundColor: '#F7F8FC' }"
:border-bottom="false"
>
<view slot="right"> 424242 </view>
</u-navbar>
<view class="selectTab">
<view class="tab-item">
<view class="label">已完成计划</view>
<view class="count">12</view>
</view>
<view class="tab-item">
<view class="label">未完成计划</view>
<view class="count">5</view>
</view>
</view>
<view class="planList">
<view class="plan-item">
<view class="time">
<u-icon
label="uView"
size="40"
name="https://cdn.uviewui.com/uview/example/button.png"
></u-icon>
<view class="date">05-21</view>
</view>
<view class="place-time">
<view class="place">校门A区教学楼</view>
<u-tag text="9:00~12:00" type="info" border-color="transparent" />
</view>
<view class="illustrate">
北大门右侧操场跑道/南广场升旗台/A区第二食堂3楼
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {};
},
created() {},
methods: {},
};
</script>
<style lang="scss" scoped>
.content {
height: 100vh;
background: #f7f8fc;
.selectTab{
padding: 42rpx 90rpx;
display: flex;
justify-content: space-between;
// background: ;
.tab-item{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50%;
background: #fff;
.label{
font-size: 28rpx;
color: #333;
}
.count{
font-size: 24rpx;
color: #999;
}
}
}
.planList {
padding: 0 32rpx;
.plan-item {
margin-top: 32rpx;
padding: 32rpx;
background: #fff;
}
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<view class="content">
<u-form ref="uForm">
<u-form-item label="反馈内容:" label-width="150" >
<u-input v-model="content" type="textarea"/>
</u-form-item>
<u-form-item label="上传图片:" label-width="150" >
<u-upload upload-text="选择或拍照" width="180" height="180" ref="uUpload" :action="action" :fileList="fileList" :form-data="form_data" ></u-upload>
</u-form-item>
<u-button type="primary" @click="submit">确定</u-button>
</u-form>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
action: '/api/index/upload_cos',
content : '',
fileList:[],
form_data : {
floder : 'photo'
},
}
},
onLoad(op) {
},
onReady(){
},
methods: {
submit(){
let _uploadPhoto_data = {}
var _list = this.$refs.uUpload.lists
for (let i = 0; i < _list.length; i++) {
_uploadPhoto_data[i] = {
url: _list[i].response.data.file
}
}
const param={
photo :JSON.stringify(_uploadPhoto_data),
content: this.content
}
this.$api.feedback(param).then(res => {
if(res.code==0){
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},500)
console.log('提交成功');
}else{
toast(res.msg)
console.log('提交失败');
}
})
}
}
}
</script>
<style lang="scss">
page{background: #f2f2f2;}
.content{margin: 10rpx;background: #fff;padding:20rpx}
</style>

69
pages/my/about/index.vue Normal file
View File

@ -0,0 +1,69 @@
<template>
<view class="content">
<view class="logo">
<u-image src="@/static/logo.png" width="150rpx" height="150rpx" mode="aspectFill"></u-image>
</view>
<u-cell-item icon="info-circle-fill" bg-color="#fff" title="当前版本" hover-class="none" :value="version" :arrow="false"></u-cell-item>
<u-cell-group title="其他:">
<u-cell-item icon="home-fill" title="公司简介" @click="about"></u-cell-item>
<u-cell-item icon="integral-fill" title="公司资质" @click="Qualification"></u-cell-item>
<u-cell-item icon="file-text-fill" title="服务协议" @click="serviceAgreement" ></u-cell-item>
<u-cell-item icon="file-text-fill" title="隐私政策" @click="privacyPolicy"></u-cell-item>
</u-cell-group>
<u-cell-group title="联系:">
<u-cell-item icon="server-fill" title="联系我们" @click="contactUs"></u-cell-item>
<u-cell-item icon="chat-fill" title="意见反馈" @click="feedback"></u-cell-item>
</u-cell-group>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, Jump,getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
version:"",
}
},
onLoad(op) {
//this.id = op.id
},
onShow() {
},
onReady() {
const that = this
uni.getSystemInfo({
success: function (res) {
console.log("res", res)
that.version = res.appVersion
}
})
},
methods: {
about(){
Jump('/pages/public/page?id=1')
},
Qualification(){
Jump('/pages/public/page?id=2')
},
serviceAgreement(){
Jump('/pages/public/page?id=3')
},
privacyPolicy(){
Jump('/pages/public/page?id=4')
},
contactUs(){
Jump('/pages/public/page?id=5')
},
feedback(){
useRouter('/pages/my/about/feedback',{} ,'navigateTo')
},
}
}
</script>
<style lang="scss">
page{background:#f2f2f2}
.logo{width: 150rpx;height: 150rpx; margin:0 auto;margin-top: 30rpx; margin-bottom: 30rpx;}
</style>

116
pages/my/account/email.vue Normal file
View File

@ -0,0 +1,116 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="原邮箱:" labelWidth="160">{{baseInfo.email}}</u-form-item>
<u-form-item label="新邮箱:" labelWidth="160" prop="new_email">
<u-input placeholder="请输入新邮箱" v-model="form.new_email" />
</u-form-item>
<u-form-item label="验证码:" labelWidth="160" prop="code">
<u-input placeholder="请输入验证码" v-model="form.code" />
<u-verification-code :seconds="seconds" ref="uCode"
@change="codeChange"></u-verification-code>
<u-button slot="right" type="primary" size="mini" @click="getCode">{{tips}}</u-button>
</u-form-item>
<u-button type="primary" @click="submit">确定</u-button>
</u-form>
</view>
</template>
<script>
import {toast, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
baseInfo: {
email : '',
},
form: {
new_email: '',
code: ''
},
rules: {
new_email: [
{
required: true,
type: 'email',
message: '邮箱格式错误',
trigger: 'blur,change'
}
],
code: [
{
required: true,
min: 4,
type: 'number',
message: '验证码格式错误',
trigger: 'change'
}
]
},
seconds: 60,
refCode: null,
tips:'获取验证码'
};
},
onLoad(){
this.getUserInfo()
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
methods: {
getUserInfo() {
this.$api.baseInfo().then(res => {
this.baseInfo = res.data
})
},
submit(){
this.$refs.uForm.validate(valid => {
if (valid) {
//TODO
this.$api.emailBind(this.form).then(res => {
if (res.code == 0) {
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},500)
} else {
toast(res.msg)
}
})
} else {
console.log('验证失败');
}
});
},
codeChange(text) {
this.tips = text;
},
getCode() {
if(this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
})
setTimeout(() => {
uni.hideLoading();
// this.start()
this.$u.toast('验证码已发送');
//
this.$refs.uCode.start();
}, 2000);
} else {
this.$u.toast('倒计时结束后再发送');
}
}
}
};
</script>
<style lang="scss" scoped>
.content{padding:35rpx}
</style>

View File

@ -0,0 +1,89 @@
<template>
<view class="content">
<u-cell-group title="帐号绑定:">
<u-cell-item icon="account-fill" title="手机绑定" :value="baseInfo.phone" @click="phone"></u-cell-item>
<u-cell-item icon="email-fill" title="邮箱绑定" :value="baseInfo.email" @click="email"></u-cell-item>
</u-cell-group>
<u-cell-group title="密码:">
<u-cell-item icon="lock-fill" title="登陆密码" value="修改" @click="modifypassword"></u-cell-item>
</u-cell-group>
<u-cell-group title="帐号:">
<u-cell-item icon="minus-people-fill" title="注销帐号" value="" @click="logout_account"></u-cell-item>
</u-cell-group>
<u-button @click="showModal=true" type="primary" style="margin-top: 20rpx;width:98%">
退出登陆
</u-button>
<u-modal v-model="showModal" showCancelButton title="退出登录" content='确定要退出吗?' @cancel="showModal=false" @confirm="confirmExit"></u-modal>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
baseInfo: {
name:'',
phone:'',
avatar:'',
email : '',
company_name:'',
registerDate:'',
login_code:'',
auth: 0,
},
version:"",
showModal: false,
}
},
onLoad(op) {
//this.id = op.id
},
onShow() {
},
onReady() {
this.getUserInfo()
const that = this
uni.getSystemInfo({
success: function (res) {
console.log("res", res)
that.version = res.appVersion
}
})
},
methods: {
getUserInfo() {
this.$api.baseInfo().then(res => {
this.baseInfo = res.data
})
},
confirmExit() {
uni.removeStorageSync('token')
this.showModal =false
useRouter('/pages/public/login',{} ,'reLaunch')
},
modifypassword(){
useRouter('/pages/my/account/password',{} ,'navigateTo')
},
email(){
useRouter('/pages/my/account/email',{} ,'navigateTo')
},
phone(){
useRouter('/pages/my/account/phone',{} ,'navigateTo')
},
logout_account(){
useRouter('/pages/my/account/logout_account',{} ,'navigateTo')
}
}
}
</script>
<style lang="scss" scoped>
page{background:#f2f2f2}
</style>

View File

@ -0,0 +1,82 @@
<template>
<view class="content">
<view class="u-content">
<u-parse :html="content"></u-parse>
</view>
<view class="tipsArea">
<view class="operation">
<u-checkbox v-model="agree">我同意<<注销协议>>提交后帐号将不再恢复</u-checkbox>
</view>
<view class="btn">
<u-button type="primary" style="width: 98%;margin-bottom: 15rpx;" @click="confirme">注销帐号</u-button>
</view>
</view>
<u-modal v-model="showModal" showCancelButton title="注销" content='确定要注销帐号吗?' @cancel="closeShowModal" @confirm="confirmOperation"></u-modal>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
content: '',
show:true,
agree : false,
showModal: false
}
},
onLoad(op) {
//this.id = op.id
this.getPage()
},
onShow() {
},
onReady() {
},
methods: {
getPage(){
const param={
id:6
}
this.$api.page(param).then(res => {
if (res.code ==1 ) {
this.content = res.data.content
}
})
},
confirme(){
if(!this.agree) return toast('请查看并同意相关协议');
this.showModal = true
},
confirmOperation() {
this.$api.logout_account().then(res => {
if (res.code ==0 ) {
toast(res.msg);
}
})
},
closeShowModal(){
this.showModal = false
}
}
}
</script>
<style lang="scss">
.u-content{
padding: 20rpx;
color: $u-content-color;
font-size: 28rpx;
line-height: 1.8;
padding-bottom: 250rpx;
}
.tipsArea{position: fixed; bottom: 0;background: #fdf6ec;width: 100%;}
.operation{padding:20rpx;padding-top: 20rpx;
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="原密码:" labelWidth="120" prop="password">
<u-input placeholder="请输入原密码" v-model="form.password" />
</u-form-item>
<u-form-item label="新密码:" labelWidth="120" prop="news_password">
<u-input placeholder="请输入要修改的新密码" v-model="form.news_password" />
</u-form-item>
<u-button type="primary" @click="submit">确定</u-button>
</u-form>
</view>
</template>
<script>
import {toast, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
form: {
password: '',
new_password :''
},
rules: {
password: [
{
required: true,
message: '请输入原密码',
trigger: 'blur,change'
}
],
news_password: [
{
required: true,
message: '请输入新密码',
trigger: 'blur,change'
}
]
},
};
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
methods: {
submit(){
this.$refs.uForm.validate(valid => {
if (valid) {
//TODO
this.$api.password(this.form).then(res => {
if (res.code == 0) {
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},500)
} else {
toast(res.msg)
}
})
} else {
console.log('验证失败');
}
});
}
}
};
</script>
<style lang="scss" scoped>
.content{padding:35rpx}
</style>

120
pages/my/account/phone.vue Normal file
View File

@ -0,0 +1,120 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="原手机号:" labelWidth="160">{{baseInfo.phone}}</u-form-item>
<u-form-item label="新手机号:" labelWidth="160" prop="new_phone">
<u-input placeholder="请输入手机号" v-model="form.new_phone" />
</u-form-item>
<u-form-item label="验证码:" labelWidth="160" prop="code">
<u-input placeholder="请输入验证码" v-model="form.code" />
<u-verification-code :seconds="seconds" ref="uCode"
@change="codeChange"></u-verification-code>
<u-button slot="right" type="primary" size="mini" @click="getCode">{{tips}}</u-button>
</u-form-item>
<u-button type="primary" @click="submit">确定</u-button>
</u-form>
</view>
</template>
<script>
import {toast, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
baseInfo: {
phone : '',
},
form: {
new_phone: '',
code: ''
},
rules: {
new_phone: [
{
required: true,
message: '请输入手机号',
trigger: 'blur,change'
},
{
validator: (rule, value, callback) => {
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
trigger: ['change','blur'],
}
],
code: [
{
required: true,
min: 4,
type: 'number',
message: '验证码格式错误',
trigger: 'change'
}
]
},
seconds: 60,
refCode: null,
tips:'获取验证码'
};
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
onLoad(){
this.getUserInfo()
},
methods: {
submit(){
this.$refs.uForm.validate(valid => {
if (valid) {
//TODO
this.$api.phoneBind(this.form).then(res => {
if (res.code == 0) {
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},500)
} else {
toast(res.msg)
}
})
} else {
console.log('验证失败');
}
});
},
getUserInfo() {
this.$api.baseInfo().then(res => {
this.baseInfo = res.data
})
},
codeChange(text) {
this.tips = text;
},
getCode() {
if(this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
})
setTimeout(() => {
uni.hideLoading();
// this.start()
this.$u.toast('验证码已发送');
//
this.$refs.uCode.start();
}, 2000);
} else {
this.$u.toast('倒计时结束后再发送');
}
}
}
};
</script>
<style lang="scss" scoped>
.content{padding:35rpx}
</style>

View File

@ -0,0 +1,38 @@
<template>
<view class="content">
<view class="">
<u-cell-group>
<u-cell-item title="通知" :arrow="false">
<u-switch v-model="costs_display" @change="costs_display_change"></u-switch>
</u-cell-item>
</u-cell-group>
</view>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
costs_display: false,
}
},
onLoad(op) {
this.costs_display = getStorageSync("costs_display") || false
},
onShow() {
},
onReady() {
},
methods: {
costs_display_change(status){
setStorageSync("costs_display",status)
console.log('status' ,getStorageSync("costs_display"))
}
}
}
</script>
<style lang="scss" scoped>
.content{padding: 30rpx;}
</style>

108
pages/my/auth.vue Normal file
View File

@ -0,0 +1,108 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="姓名:" label-width="170" prop="cardno">
<u-input v-model="form.name" placeholder="请输入姓名"/>
</u-form-item>
<u-form-item label="身份证号:" label-width="170" prop="cardno">
<u-input v-model="form.cardno" placeholder="请输入身份证号"/>
</u-form-item>
<u-form-item label="身份证正反面:" label-width="200">
<u-upload upload-text="选择或拍照" width="180" height="180" ref="uUpload" :action="action" :fileList="fileList" :form-data="form_data" ></u-upload>
</u-form-item>
<view class="submit_con">
<u-button type="primary" @click="submit">提交</u-button>
</view>
</u-form>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync,getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
action: this.$api_url + '/api/index/upload_cos',
fileList:[],
form: {
cardno: '',
photo:'',
name:''
},
form_data : {
floder : 'photo'
},
rules: {
cardno: [
{
required: true,
message: '请输入姓名',
//
trigger: ['change','blur'],
}
],
cardno: [
{
required: true,
message: '请输入身份证号码',
//
trigger: ['change','blur'],
}
]
}
};
},
onReady(){
this.$refs.uForm.setRules(this.rules);
},
methods: {
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
//
let _uploadPhoto_data = {}
var _list = this.$refs.uUpload.lists
for (let i = 0; i < _list.length; i++) {
_uploadPhoto_data[i] = {
url: _list[i].response.data.file
}
}
this.form.photo = JSON.stringify(_uploadPhoto_data),
this.$api.auth(this.form).then(res => {
if(res.code==0){
toast(res.msg)
setTimeout(()=>{
uni.$emit('refresh', {}) //
uni.navigateBack()
},500)
console.log(res);
}else{
toast(res.msg)
console.log(res);
}
})
console.log(this.form)
} else {
console.log('验证失败');
}
})
}
},
}
</script>
<style lang="scss">
.content{padding:30rpx}
</style>

172
pages/my/index.vue Normal file
View File

@ -0,0 +1,172 @@
<template>
<view class="content">
<view class="head">
<u-avatar :src="baseInfo.avatar" size="140" mode="circle" class="avatar" :show-level="true" level-icon ="camera" @click="upload_avatar"></u-avatar>
<view class="personInfo">
<view>{{baseInfo.name}}</view>
<view class="account">帐号{{baseInfo.phone}}</view>
</view>
<view class="auth">
<u-tag text="已实名" shape="circleLeft" type="success" v-if="baseInfo.auth==1"/>
<u-tag text="未实名" shape="circleLeft" type="error" v-if="baseInfo.auth==0"/>
</view>
</view>
<u-cell-group>
<u-cell-item icon="account-fill" title="个人信息" @click="profile"></u-cell-item>
<u-cell-item icon="lock-fill" title="帐号&安全" @click="account"></u-cell-item>
<u-cell-item icon="setting-fill" title="设置" @click="setting"></u-cell-item>
<u-cell-item icon="server-fill" title="服务中心" @click="service_center"></u-cell-item>
<u-cell-item icon="info-circle" title="关于我们" @click="about"></u-cell-item>
</u-cell-group>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data(){
return {
baseInfo: {
name:'',
phone:'',
avatar:'',
company_name:'',
registerDate:'',
login_code:'',
auth: 0,
},
version:"",
}
},
onLoad() {
this.getUserInfo()
const that = this
uni.getSystemInfo({
success: function (res) {
console.log("res", res)
that.version = res.appVersion
}
})
},
onShow() {
uni.$on('refresh', e => {
this.getUserInfo()
uni.$off('refresh')
})
},
//
onPullDownRefresh() {
//console.log('refresh');
this.getUserInfo()
uni.stopPullDownRefresh();//
},
methods: {
upload_avatar(){
const _this=this
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'], //
sourceType: ['album','camera'], //
success: function (res) {
uni.uploadFile({
url: _this.$api_url + '/api/index/upload_cos',
filePath: res.tempFilePaths[0],
name: 'file',
formData: {
floder: 'avatar'
},
success: (uploadFileRes) => {
//let retData = JSON.parse(uploadFileRes)
let retData = JSON.parse(uploadFileRes.data)
console.log('up:',retData)
const _data ={
avatar: retData.data.file
}
_this.$api.baseInfoSave(_data).then(ret => {
if (ret.code == 1) {
_this.baseInfo.avatar = retData.data.file
} else {
toast(res.msg)
}
})
}
});
}
});
},
getUserInfo() {
this.$api.baseInfo().then(res => {
//console.log(res)
this.baseInfo = res.data
this.avatar_src= res.data.avatar
})
},
service_center(){
toast('未设置跳转,请自行定义')
},
profile(){
useRouter('/pages/my/profile',{} ,'navigateTo')
},
account(){
useRouter('/pages/my/account/index',{} ,'navigateTo')
},
setting(){
useRouter('/pages/my/account/setting',{} ,'navigateTo')
},
about(){
useRouter('/pages/my/about/index',{} ,'navigateTo')
},
}
}
</script>
<style lang="scss" scoped>
.content{
.head{
background-color:rgb(71, 144, 255);
display: flex;
min-height: 300rpx;
padding-top: 60rpx;
.avatar{
margin-left: 50rpx;
}
.personInfo{
color:#fff;
margin-top: 25rpx;
font-size: 30rpx;
margin-left: 30rpx;
line-height: 50rpx;
.account{font-size: 26rpx;}
}
.auth{flex: 1;text-align: right;margin-top: 40rpx;}
}
}
</style>

70
pages/my/profile.vue Normal file
View File

@ -0,0 +1,70 @@
<template>
<view class="content">
<u-cell-group title="个人信息:">
<u-cell-item icon="account-fill" title="姓名" hover-class="none" :value="baseInfo.name" :arrow="false"></u-cell-item>
<u-cell-item icon="phone-fill" title="手机" hover-class="none" :value="baseInfo.phone" :arrow="false"></u-cell-item>
</u-cell-group>
<u-cell-group title="实名:">
<u-cell-item icon="integral-fill" title="实名认证" :value="auth_txt" @click="auth"></u-cell-item>
</u-cell-group>
<u-cell-group title="其他信息:">
<u-cell-item icon="home-fill" title="所属公司" hover-class="none" :value="baseInfo.company_name" :arrow="false"></u-cell-item>
<u-cell-item icon="coupon-fill" title="标识号" hover-class="none" :value="baseInfo.login_code" :arrow="false"></u-cell-item>
<u-cell-item icon="calendar-fill" title="注册时间" hover-class="none" :value="baseInfo.registerDate" :arrow="false"></u-cell-item>
</u-cell-group>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
baseInfo: {
name:'',
phone:'',
avatar:'',
company_name:'',
registerDate:'',
login_code:'',
auth: 0,
},
auth_txt: '未实名'
}
},
onLoad(op) {
//this.id = op.id
},
onShow() {
},
onReady() {
this.getUserInfo()
},
methods: {
getUserInfo() {
this.$api.baseInfo().then(res => {
this.baseInfo = res.data
if(this.baseInfo.auth==1){
this.auth_txt ="已实名"
}
})
},
auth(){
if(!this.baseInfo.auth){
useRouter('/pages/my/auth',{} ,'navigateTo')
}else{
toast('已实名认证')
}
},
}
}
</script>
<style lang="scss" scoped>
page{background:#f2f2f2}
</style>

View File

@ -0,0 +1,130 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="手机号:" labelWidth="160" prop="phone">
<u-input placeholder="请输入手机号" v-model="form.phone" />
</u-form-item>
<u-form-item label="验证码:" labelWidth="160" prop="code">
<u-input placeholder="请输入验证码" v-model="form.code" />
<u-verification-code :seconds="seconds" ref="uCode"
@change="codeChange"></u-verification-code>
<u-button slot="right" type="primary" size="mini" @click="getCode">{{tips}}</u-button>
</u-form-item>
<u-form-item label="密码:" labelWidth="160" prop="password">
<u-input placeholder="请输入要重置的密码" v-model="form.password" />
</u-form-item>
<u-button type="primary" @click="submit">确定</u-button>
</u-form>
</view>
</template>
<script>
import {toast, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
baseInfo: {
phone : '',
},
form: {
phone: '',
code: '',
password:''
},
rules: {
phone: [
{
required: true,
message: '请输入手机号',
trigger: 'blur,change'
},
{
validator: (rule, value, callback) => {
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
trigger: ['change','blur'],
}
],
code: [
{
required: true,
min: 4,
type: 'number',
message: '验证码格式错误',
trigger: 'change'
}
],
password: [
{
required: true,
min: 6,
message: '请输入密码',
trigger: 'change'
}
]
},
seconds: 60,
refCode: null,
tips:'获取验证码'
};
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
onLoad(){
},
methods: {
submit(){
this.$refs.uForm.validate(valid => {
if (valid) {
//TODO
this.$api.forgetPassWord(this.form).then(res => {
if (res.code == 0) {
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},500)
} else {
toast(res.msg)
}
})
} else {
console.log('验证失败');
}
});
},
codeChange(text) {
this.tips = text;
},
getCode() {
if(this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
})
setTimeout(() => {
uni.hideLoading();
// this.start()
this.$u.toast('验证码已发送');
//
this.$refs.uCode.start();
}, 2000);
} else {
this.$u.toast('倒计时结束后再发送');
}
}
}
};
</script>
<style lang="scss" scoped>
.content{padding:35rpx}
</style>

120
pages/public/login.vue Normal file
View File

@ -0,0 +1,120 @@
<template>
<view class="content">
<view class="logo">
<image style="width: 150rpx; height: 150rpx; " src="/static/images/logo.png" alt="" /></image>
</view>
<view class="login">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="帐号:" prop="phone">
<u-input v-model="form.phone" placeholder="请输入登陆账号" />
</u-form-item>
<u-form-item label="密码:" prop="password">
<u-input type="password" v-model="form.password" placeholder="请输入密码"/>
</u-form-item>
</u-form>
<u-button @click="submit" style="margin-top: 30rpx;" type="primary">登录</u-button>
<view class="forgetPassword" @click="forgetPassWord">忘记密码</view>
<view class="reg" @click="register">没有帐号注册</view>
</view>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
form: {
phone: '',
password: '',
},
rules: {
phone: [
{
required: true,
message: '请输入帐号',
//
trigger: ['change','blur'],
}
],
password: [
{
required: true,
message: '请输入密码',
//
trigger: ['change','blur'],
}
]
}
};
},
methods: {
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
const data = {
login_code: this.form.login_code,
phone: this.form.phone,
password: this.form.password,
}
this.$api.login(data).then(res => {
console.log(res)
if (res.code !== 0) {
toast(res.msg)
} else {
toast('登录成功')
setStorageSync('token' ,res.data.token )
useRouter('/pages/index/index',{} ,'switchTab')
}
})
} else {
console.log('验证失败');
}
});
},
register(){
useRouter('/pages/public/register')
},
forgetPassWord(){
useRouter('/pages/public/forget_password')
}
},
// onReadyonLoad
onReady() {
this.$refs.uForm.setRules(this.rules);
}
};
</script>
<style lang="scss" scoped>
.content {
padding: 0 10rpx;
width:100%;
background-image: url('/static/images/login_bg.png') ;
background-size: cover;
background-repeat: no-repeat;
background-position: left top;
}
.logo{
width:150rpx;
margin:0 auto;
padding-top:140rpx;
}
.login{
width: 80%;
margin: 0 auto;
margin-top: 260rpx;
}
.title {
text-align: center;
margin: 80rpx 0 0 0;
}
.reg{float: right;padding:30rpx 0 30rpx 0;color:#2979ff;}
.forgetPassword{float: left;padding:30rpx 0 30rpx 0;color:#2979ff;}
</style>

49
pages/public/page.vue Normal file
View File

@ -0,0 +1,49 @@
<template>
<view class="content">
<mp-html :content="html" :tag-style="tag_style"/>
<!-- <rich-text :nodes="html"></rich-text> -->
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
html: '',
tag_style:{
p: 'line-height:45rpx; margin-top: 30rpx;',
img : 'width:100%',
}
}
},
onLoad(op) {
this.id = op.id
this.getPage()
},
onShow() {
},
onReady() {
},
methods: {
getPage(){
const param = {
id : this.id
}
this.$api.page(param).then(res => {
if (res.code ==1 ) {
uni.setNavigationBarTitle({
title: res.data.title
})
this.html = res.data.content
//HTMLmp-html,https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart
}
})
},
}
}
</script>
<style lang="scss">
.content{
padding: 20rpx;
}
</style>

179
pages/public/register.vue Normal file
View File

@ -0,0 +1,179 @@
<template>
<view class="content">
<u-form :model="form" ref="uForm" :error-type="['toast']">
<u-form-item label="登陆帐号:" prop="username" label-width="170"><u-input placeholder="请输入登陆帐号" v-model="form.username" /></u-form-item>
<u-form-item label="真实姓名:" label-width="170" prop="name"><u-input placeholder="请输入真实姓名" v-model="form.name" /></u-form-item>
<u-form-item label="手机号:" label-width="170" prop="phone"><u-input placeholder="请输入输入手机号码" v-model="form.phone" /></u-form-item>
<u-form-item label="登陆密码:" label-width="170" prop="password"><u-input type="password" placeholder="请输入登陆密码" v-model="form.password" /></u-form-item>
<view class="agreement">
<u-checkbox size="40rpx" v-model="check" @change="checkboxChange"></u-checkbox>
<view class="agreement-text">
我已知晓并同意 <text @click="showPopup(4)" style="color:cornflowerblue">用户服务协议</text><text @click="showPopup(5)" style="color:cornflowerblue">隐私政策</text>
</view>
</view>
<view class="submit_con">
<u-button type="primary" @click="submit">提交注册</u-button>
</view>
</u-form>
<u-popup v-model="popupVisible" mode="bottom" length="60%" :closeable="true">
<view class="popup-content">
<rich-text :nodes="protocolsContent" style="line-height: 50rpx;"></rich-text>
</view>
<view class="popup-footer">
<u-button class="close_pop" size="medium" type="primary" @click="hidePopup">关闭</u-button>
</view>
</u-popup>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync,getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
action: this.$api_url + '/api/index/upload_cos',
fileList:[],
popupVisible: false,
protocolsContent: "",
check: false,
agreement: false,
form: {
username: '',
name: '',
phone: '',
cardno: '',
photo:'',
password:''
},
form_data : {
floder : 'photo'
},
rules: {
username: [
{
required: true,
message: '请输入登陆帐号',
//
trigger: ['change','blur'],
}
],
password: [
{
required: true,
message: '请输入密码',
//
trigger: ['change','blur'],
}
],
name: [
{
required: true,
message: '请输入真实姓名',
//
trigger: ['change','blur'],
}
],
phone: [
{
required: true,
type:"number",
message: '请输入登陆手机号',
//
trigger: ['change','blur'],
}
],
}
};
},
onReady(){
this.$refs.uForm.setRules(this.rules);
},
methods: {
checkboxChange(e) {
this.agreement = e.value;
},
showPopup(id) {
this.popupVisible = true;
const params ={id:id}
this.$api.page(params).then(res => {
//console.log('list', res.data);
this.protocolsContent = res.data.content
})
},
hidePopup() {
this.popupVisible = false;
},
submit() {
this.$refs.uForm.validate(valid => {
if (valid) {
if(!this.agreement) return toast('请查看并同意相关协议');
this.$api.register(this.form).then(res => {
if(res.code==0){
toast(res.msg)
setTimeout(()=>{
uni.navigateBack()
},1500)
console.log(res);
}else{
toast(res.msg)
console.log(res);
}
})
console.log(this.form)
} else {
console.log('验证失败');
}
})
}
},
}
</script>
<style lang="scss">
.popup-content {
padding: 40rpx;
padding-bottom: 120rpx;
}
.popup-footer {
margin-top: 20rpx;
text-align: center;
position: fixed;
bottom: 0;
width: 100%;
}
.content{padding:30rpx}
.agreement {
display: flex;
margin: 40rpx 0 0 10rpx;
.agreement-text {
padding-left: 0rpx;
color: $u-tips-color;
}
}
.submit_con{margin-top: 20rpx;}
</style>

View File

@ -0,0 +1,136 @@
<template>
<view class="content">
<view class="banner" auto-focus>
<image class="banner-img" :src="detail.thumb"></image>
<view class="title-area">
<text class="title-text">{{detail.title}}</text>
</view>
</view>
<view class="article-meta">
<text class="article-meta-text article-author">{{detail.source}}</text>
<text class="article-meta-text article-text">发表于</text>
<text class="article-meta-text article-time">{{detail.addtime}}</text>
</view>
<view class="article-content">
<view v-html="html"></view>
</view>
<view class="comment-wrap"></view>
</view>
</template>
<script>
export default {
data() {
return {
detail: [],
html: ''
}
},
onLoad(op) {
this.id = op.id
this.getInfo()
},
onReady() {
},
methods: {
getInfo(){
const params = {
id : this.id
}
this.$api.detail(params).then(res => {
if (res.code == 0) {
this.detail = res.data
this.html = res.data.content
console.log( this.html)
}else{
}
})
},
}
}
</script>
<style lang="scss" scoped>
page {
min-height: 100%;
}
.banner {
height: 380rpx;
position: relative;
background-color: #ccc;
flex-direction: row;
overflow: hidden;
}
.banner-img {
width:100%
}
.title-area {
position: absolute;
left: 15rpx;
right: 15rpx;
bottom: 15rpx;
z-index: 11;
}
.title-text {
font-size: 40rpx;
font-weight: 400;
line-height: 20rpx;
lines: 2;
color: #ffffff;
overflow: hidden;
}
.article-meta {
padding: 10rpx 15rpx;
font-size: 30rpx;
}
.article-meta-text {
color: gray;
font-size: 30rpx;
}
.article-text {
font-size: 32rpx;
line-height: 25rpx;
margin: 0 10rpx;
}
.article-author {
font-size: 32rpx;
}
.article-time {
font-size:30rpx;
}
.article-content {
font-size: 36rpx;
padding: 0 35rpx;
margin-bottom: 35rpx;
overflow: hidden;
color: #333;
}
.article-content img {
max-width: 100%;
}
.article-content p{line-height:40rpx; margin-top: 40rpx;}
</style>

73
pages/tab_list.vue Normal file
View File

@ -0,0 +1,73 @@
<template>
<view class="content">
<z-paging ref="paging" v-model="dataList" @query="queryList">
<template #top>
<z-tabs :list="tabList" @change="tabChange" />
</template>
<!-- 如果希望其他view跟着页面滚动可以放在z-paging标签内 -->
<view class="" v-for="(item,index) in dataList" :key="index" @click="itemClick(item)">
<u-card :title="item.store_name" :sub-title="item.status" >
<view class="" slot="body">
<view class="content"><strong>申请</strong>{{item.master_id}}</view>
<view class="content"><strong>时间</strong>{{item.addtime}}</view>
<view class="content"><strong>数量</strong>{{item.total_nums}}</view>
</view>
</u-card>
</view>
</z-paging>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
dataList: [],
tabList: ['全部','已审核','待审核'],
tabIndex: 0,
}
},
onLoad(op) {
//this.id = op.id
},
onShow() {
},
onReady() {
},
methods: {
tabChange(index) {
this.tabIndex = index;
this.$refs.paging.reload(true);
},
queryList(pageNo, pageSize) {
const params = {
page: pageNo,
limit: pageSize,
type: this.tabIndex + 1
}
this.$api.outList(params).then(res => {
if (res.code == 1) {
//z-paging
this.$refs.paging.complete(res.data);
}else{
this.$refs.paging.complete(false);
}
})
},
itemClick(item) {
//console.log(item)
uni.navigateTo({
url:'./detail?id='+item.id
});
}
}
}
</script>
<style lang="scss" scoped>
page{background:#f2f2f2}
.content{padding: 20rpx;}
</style>

31
pages/templates.vue Normal file
View File

@ -0,0 +1,31 @@
<template>
<view class="content">
<view class="">
</view>
</view>
</template>
<script>
import {toast, clearStorageSync, setStorageSync, getStorageSync, useRouter} from '@/utils/utils.js'
export default {
data() {
return {
id: 0
}
},
onLoad(op) {
//this.id = op.id
},
onShow() {
},
onReady() {
},
methods: {
}
}
</script>
<style lang="scss" scoped>
page{background:#f2f2f2}
.content{padding: 20rpx;}
</style>

View File

@ -0,0 +1 @@
可把模板放在 \HBuilder X\templates\file\vue-page目录下面这样可以在新建页面中快速创建。

131
readme.md Normal file
View File

@ -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。

BIN
static/images/area-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

BIN
static/images/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
static/images/list_cur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
static/images/login_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
static/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
static/images/plan-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
static/images/pos-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
static/images/update.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
static/tab/home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

BIN
static/tab/home_cur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

BIN
static/tab/list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 B

BIN
static/tab/list_cur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

BIN
static/tab/my.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

BIN
static/tab/my_cur.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

27
store/$u.mixin.js Normal file
View File

@ -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)
}
}

64
store/index.js Normal file
View File

@ -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

76
uni.scss Normal file
View File

@ -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;

88
utils/common.js Normal file
View File

@ -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
}

50
utils/interceptor.js Normal file
View File

@ -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) {
}
})

62
utils/request.js Normal file
View File

@ -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

66
utils/requestManager.js Normal file
View File

@ -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

475
utils/utils.js Normal file
View File

@ -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;
}
}