main
15004070936 3 weeks ago
parent 607a7a6468
commit ff6552eccd

@ -1,40 +1,83 @@
<template> <template>
<view class="loginBack"> <view class="loginBack">
<view class="loginForm"> <view class="loginForm">
<view class="welcomeLogin">欢迎登录</view> <view class="welcomeLogin">欢迎登录</view>
<view class="tip">yoga</view> <view class="tip">瑜伽汇</view>
<view class="formregion">
<uni-forms :model="form" ref="uForm"> <!-- 登录方式切换 Tabs-->
<view class="login-tabs">
<uni-forms-item prop="phonenumber" > <view class="tab-item" :class="{ active: loginType === 'pwd' }" @click="loginType = 'pwd'">
<uni-easyinput type="number" placeholder="请输入手机号码" v-model="form.phonenumber" @blur="zhhandleBlur" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle"/> 密码登录
</uni-forms-item> </view>
<view class="tab-item" :class="{ active: loginType === 'sms' }" @click="loginType = 'sms'">
<uni-forms-item prop="password" > 短信登录(待审核)
<uni-easyinput type="password" placeholder="请输入密码" v-model="form.password" @blur="mmhandleBlur" maxlength="12" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle"/> </view>
</uni-forms-item> </view>
<uni-forms-item prop="code" > <view class="formregion">
<uni-easyinput type="text" placeholder="请输入验证码" v-model.trim="form.code" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle"/> <uni-forms :model="form" ref="uForm">
<image :src="checkimg" class="checkimg" mode="scaleToFill"></image>
<view class="codebtn" @click="getcode">?</view> <!-- 手机号共用 -->
</uni-forms-item> <uni-forms-item prop="phonenumber">
<uni-easyinput type="number" placeholder="请输入手机号码" v-model="form.phonenumber" @blur="zhhandleBlur"
<button type="primary" class="logincla" @click="loginpwdDo"></button> :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle" />
<view v-if="iflogin" class="loginno">..</view> </uni-forms-item>
<view class="regcon"> <!-- &lt;!&ndash; 密码登录密码 + 图形验证码 &ndash;&gt;-->
<!-- <view class="regtxt" @click="gotopwd"></view> <block v-if="loginType === 'pwd'">
<view class="regtxtl"></view> --> <uni-forms-item prop="password">
<view class="regtxtr" @click="gotoregister"> </view> <uni-easyinput type="password" placeholder="请输入密码" v-model="form.password" @blur="mmhandleBlur"
</view> maxlength="12" :inputBorder="false" :clearable="false" :placeholderStyle="placeholderStyle" />
</uni-forms-item>
</uni-forms>
<uni-forms-item prop="code">
</view> <uni-easyinput type="text" placeholder="请输入验证码" v-model.trim="form.code" :inputBorder="false"
<u-toast ref="uToast" /> :clearable="false" :placeholderStyle="placeholderStyle" />
</view> <image :src="checkimg" class="checkimg" mode="scaleToFill"></image>
</view> <view class="codebtn" @click="getcode">?</view>
</uni-forms-item>
<button type="primary" class="logincla" @click="loginpwdDo"></button>
</block>
<!-- 短信登录短信验证码 -->
<block v-if="loginType === 'sms'">
<uni-forms-item prop="smsCode">
<!-- 新增一层相对定位容器作为绝对定位按钮的参照物 -->
<view class="input-wrap">
<uni-easyinput
type="text"
placeholder="请输入短信验证码"
v-model.trim="form.smsCode"
:inputBorder="false"
:clearable="false"
:placeholderStyle="placeholderStyle"
/>
<view
class="sms-code-btn"
@click="sendSmsCode"
:class="{ disabled: smsCountdown > 0 }"
>
{{ smsCountdown > 0 ? smsCountdown + 's' : '获取验证码' }}
</view>
</view>
</uni-forms-item>
<button type="primary" class="logincla" @click="loginSmsDo"></button>
</block>
<view v-if="iflogin" class="loginno">..</view>
<view class="regcon">
<!-- <view class="regtxtr" @click="gotoregister"> </view>-->
</view>
</uni-forms>
</view>
<u-toast ref="uToast" />
</view>
</view>
</template> </template>
<script> <script>
@ -50,6 +93,7 @@
form: { form: {
phonenumber:"", phonenumber:"",
password:"", password:"",
smsCode: "", //
code:"", code:"",
uuid:"", uuid:"",
registerId:"", // id registerId:"", // id
@ -60,6 +104,9 @@
address:'', address:'',
longitude:'', // longitude:'', //
latitude:'', // latitude:'', //
loginType:'pwd',
smsCountdown: 0, //
smsTimer: null, //
} }
}, },
onLoad(){ onLoad(){
@ -299,12 +346,12 @@
}); });
return false; return false;
} }
if(uni.getStorageSync("register_id")){ if(uni.getStorageSync("register_id")){
this.form.registerId=uni.getStorageSync("register_id"); this.form.registerId=uni.getStorageSync("register_id");
this.$forceUpdate(); this.$forceUpdate();
} }
uni.setStorageSync('iftoken', "1"); uni.setStorageSync('iftoken', "1");
uni.showLoading({ uni.showLoading({
title: '正在登录...' title: '正在登录...'
@ -403,6 +450,154 @@
myCache('shoplist',shoplist); myCache('shoplist',shoplist);
} }
}, },
// ========== ==========
/**
* 发送短信验证码
*/
async sendSmsCode() {
// 1.
const phone = this.form.phonenumber;
if (!this.$u.test.mobile(phone)) {
this.$refs.uToast.show({
title: '请输入正确的手机号码',
type: 'warning',
duration: 2000
});
return;
}
console.log(12)
// 2.
if (this.smsCountdown > 0) {
return;
}
console.log(34)
// 3.
uni.showLoading({
title: '发送中...'
});
try {
console.log(56)
const {
data: res
} = await uni.$http.post('/sms/sendCode', {
phone: phone
});
console.log(78)
uni.hideLoading();
console.log(9)
if (res && res.code === 200) {
this.$refs.uToast.show({
title: '验证码已发送',
type: 'success',
duration: 2000
});
//
this.startCountdown();
} else {
this.$refs.uToast.show({
title: res.msg || '发送失败,请重试',
type: 'error',
duration: 2000
});
}
} catch (e) {
uni.hideLoading();
this.$refs.uToast.show({
title: '发送失败,请检查网络',
type: 'error',
duration: 2000
});
}
},
/**
* 开始60秒倒计时
*/
startCountdown() {
this.smsCountdown = 60;
if (this.smsTimer) {
clearInterval(this.smsTimer);
}
this.smsTimer = setInterval(() => {
this.smsCountdown--;
if (this.smsCountdown <= 0) {
clearInterval(this.smsTimer);
this.smsTimer = null;
}
}, 1000);
},
/**
* 短信验证码登录
*/
async loginSmsDo() {
const phone = this.form.phonenumber;
const smsCode = this.form.smsCode;
if (!this.$u.test.mobile(phone)) {
this.$refs.uToast.show({
title: '请输入正确的手机号码',
type: 'warning',
duration: 2000
});
return;
}
if (!smsCode || smsCode.length !== 6) {
this.$refs.uToast.show({
title: '请输入6位短信验证码',
type: 'warning',
duration: 2000
});
return;
}
if (uni.getStorageSync("register_id")) {
this.form.registerId = uni.getStorageSync("register_id");
}
uni.setStorageSync('iftoken', "1");
uni.showLoading({
title: '登录中...'
});
try {
const {
data: res
} = await uni.$http.post('/api/sms/login', {
phonenumber: phone,
smsCode: smsCode,
registerId: this.form.registerId,
position: this.form.position
});
if (res && res.code === 200) {
uni.setStorageSync("loginindex", "1");
uni.setStorageSync("token", res.token);
//
this.getUserInfo();
this.loadshop();
this.$refs.uToast.show({
title: '登录成功',
type: 'success',
isTab: true,
duration: 2000,
url: '/pages/index/index'
});
} else {
this.$refs.uToast.show({
title: res.msg || '登录失败,请重试',
type: 'error',
duration: 2000
});
}
} catch (e) {
uni.hideLoading();
this.$refs.uToast.show({
title: '登录失败,请检查网络',
type: 'error',
duration: 2000
});
}
},
} }
} }
@ -426,7 +621,57 @@
border-radius: 254rpx 254rpx 254rpx 254rpx; border-radius: 254rpx 254rpx 254rpx 254rpx;
text-indent: 20rpx; text-indent: 20rpx;
} }
.lttxt{ /* ====== 新增 Tabs 样式 ====== */
.login-tabs {
display: flex;
justify-content: center;
background: #fff;
border-radius: 10px 10px 0 0;
padding: 10rpx 0;
margin-top: 10rpx;
.tab-item {
flex: 1;
text-align: center;
font-size: 30rpx;
padding: 20rpx 0;
color: #999;
border-bottom: 4rpx solid transparent;
transition: all 0.3s;
&.active {
color: #00a89b;
border-bottom-color: #00a89b;
font-weight: 600;
}
}
}
/* ====== 短信验证码按钮 ====== */
.sms-code-btn {
position: absolute;
top: 12rpx; /* 与 codebtn 对齐 */
width: 180rpx;
right: 20rpx;
display: block;
display: flex; /* 改为 flex */
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
color: #00a89b;
height: 60rpx;
line-height: 60rpx;
font-size: 26rpx;
padding: 0 20rpx;
background: #f0f0f0; /* 浅灰底,便于区分 */
border-radius: 30rpx;
/* 移除 transform避免基准偏移 */
&.disabled {
color: #999;
background: #e8e8e8;
}
}
.lttxt{
font-size: 30rpx; font-size: 30rpx;
font-family: PingFang SC, PingFang SC; font-family: PingFang SC, PingFang SC;
font-weight: 400; font-weight: 400;

@ -5,6 +5,11 @@
<view class="hback"> <view class="hback">
<uni-icons type="back" size="24" color="#000" @tap="gotoBack"></uni-icons> <uni-icons type="back" size="24" color="#000" @tap="gotoBack"></uni-icons>
<text class="htxt">我的课程</text> <text class="htxt">我的课程</text>
<view style="flex:1;"></view>
<view class="save-btn" @click="generateWeekScheduleImage" v-show="1==tabCurrentIndex0">
<uni-icons type="download" size="24" color="#00A99A"></uni-icons>
<text style="font-size:24rpx;color:#00A99A;margin-left:6rpx;">保存周课表</text>
</view>
<view class="tabtip" @click="openDialog"> <view class="tabtip" @click="openDialog">
<uni-icons class="rtop" type="info" size="21" color="#e96900"></uni-icons> <uni-icons class="rtop" type="info" size="21" color="#e96900"></uni-icons>
</view> </view>
@ -171,7 +176,9 @@
<!-- 是否登录 --> <!-- 是否登录 -->
<openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin> <openlogin ref="loginId" @getPhoneNumber="getPhoneNumber"></openlogin>
</view> <canvas canvas-id="weekCanvas" id="weekCanvas" :style="{position:'fixed',left:'-9999px',top:'0',width:canvasWidth+'px',height:canvasHeight+'px',pointerEvents:'none'}"></canvas>
</view>
</template> </template>
<script> <script>
@ -183,6 +190,9 @@
}, },
data() { data() {
return { return {
canvasWidth: 1, // 0canvas
canvasHeight: 1, //
previewImagePath: '', //
openId:"", openId:"",
phone:"", phone:"",
userid:"", userid:"",
@ -678,6 +688,292 @@
},100); },100);
}, },
methods: { methods: {
generateWeekScheduleImage() {
if (!this.columnTitles || this.columnTitles.length === 0 || !this.claTimeContainer || this.claTimeContainer.length === 0) {
uni.showToast({ title: '暂无数据可生成图片', icon: 'none' });
return;
}
const padding = 10;
const rowHeaderWidth = 80;
const cellWidth = 120;
const cellHeight = 90;
const headerHeight = 60;
const cols = this.columnTitles.length;
const rows = this.claTimeContainer.length;
const canvasWidth = padding * 2 + rowHeaderWidth + cols * cellWidth;
const canvasHeight = padding * 2 + headerHeight + rows * cellHeight;
// canvas 0
this.canvasWidth = canvasWidth;
this.canvasHeight = canvasHeight;
uni.showLoading({ title: '生成中...' });
// 8loading
const timeoutTimer = setTimeout(() => {
uni.hideLoading();
uni.showToast({ title: '生成超时,请重试', icon: 'none' });
}, 8000);
const clearTimer = () => clearTimeout(timeoutTimer);
// #ifdef H5
try {
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
if (!ctx) {
clearTimer();
uni.hideLoading();
uni.showToast({ title: '无法创建 Canvas 上下文', icon: 'none' });
return;
}
this.drawSchedule(ctx, canvasWidth, canvasHeight);
const dataURL = canvas.toDataURL('image/png');
clearTimer();
uni.hideLoading();
this.previewImagePath = dataURL;
this.$nextTick(() => {
this.$refs.previewPopup.open();
});
} catch (e) {
clearTimer();
uni.hideLoading();
console.error(e);
uni.showToast({ title: '生成失败', icon: 'none' });
}
// #endif
// #ifndef H5
// canvas DOM
this.$nextTick(() => {
setTimeout(() => {
const ctx = uni.createCanvasContext('weekCanvas', this);
this.drawSchedule(ctx, canvasWidth, canvasHeight);
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'weekCanvas',
width: canvasWidth,
height: canvasHeight,
destWidth: canvasWidth * 2,
destHeight: canvasHeight * 2,
success: (res) => {
clearTimer();
uni.hideLoading();
this.previewImagePath = res.tempFilePath;
this.$nextTick(() => {
this.$refs.previewPopup.open();
});
this.saveImage();
},
fail: (err) => {
clearTimer();
uni.hideLoading();
console.error('canvasToTempFilePath fail:', err);
uni.showToast({ title: '生成失败:' + (err.errMsg || ''), icon: 'none' });
}
}, this);
});
}, 50);
});
// #endif
},
drawSchedule(ctx, canvasWidth, canvasHeight) {
// CanvasContext (uni-app CanvasContext)
const isLegacy = typeof ctx.setFillStyle === 'function' && typeof ctx.fillStyle === 'undefined';
//
const setFill = (color) => isLegacy ? ctx.setFillStyle(color) : (ctx.fillStyle = color);
const setStroke = (color) => isLegacy ? ctx.setStrokeStyle(color) : (ctx.strokeStyle = color);
const setFontSize = (size) => isLegacy ? ctx.setFontSize(size) : (ctx.font = size + 'px sans-serif');
const setTextAlign = (align) => isLegacy ? ctx.setTextAlign(align) : (ctx.textAlign = align);
const setTextBaseline = (baseline) => isLegacy ? ctx.setTextBaseline(baseline) : (ctx.textBaseline = baseline);
const padding = 10;
const rowHeaderWidth = 80;
const cellWidth = 120;
const cellHeight = 90;
const headerHeight = 60;
const cols = this.columnTitles.length;
const rows = this.claTimeContainer.length;
//
setFill('#ffffff');
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
//
setFill('#e4f9f7');
ctx.fillRect(padding, padding, rowHeaderWidth, headerHeight);
for (let i = 0; i < cols; i++) {
const col = this.columnTitles[i];
const x = padding + rowHeaderWidth + i * cellWidth;
setFill('#e4f9f7');
ctx.fillRect(x, padding, cellWidth, headerHeight);
setFill('#000000');
setFontSize(12);
setTextAlign('center');
setTextBaseline('middle');
ctx.fillText(col.day, x + cellWidth / 2, padding + headerHeight / 2 - 8);
ctx.fillText(col.weekName, x + cellWidth / 2, padding + headerHeight / 2 + 8);
if (col.count > 0) {
setFill('#ff0000');
setFontSize(10);
ctx.fillText(col.count, x + cellWidth - 20, padding + 12);
}
}
//
for (let i = 0; i < rows; i++) {
const row = this.claTimeContainer[i];
const y = padding + headerHeight + i * cellHeight;
//
setFill('#F2F2F2');
ctx.fillRect(padding, y, rowHeaderWidth, cellHeight);
setFill('#000000');
setFontSize(12);
setTextAlign('center');
setTextBaseline('middle');
ctx.fillText(row.time, padding + rowHeaderWidth / 2, y + cellHeight / 2);
//
for (let j = 0; j < cols; j++) {
const col = this.columnTitles[j];
const x = padding + rowHeaderWidth + j * cellWidth;
let course = null;
// YYYY-MM-DD MM-DD
for (let key in row.claTimeWeekDayMap) {
const list = row.claTimeWeekDayMap[key];
if (list && list.length > 0) {
const claDate = list[0].claDate || '';
const day = col.day || '';
const matched = claDate === day ||
(day.length === 5 && claDate.length === 10 && claDate.slice(5) === day);
if (matched) {
course = list[0];
break;
}
}
}
if (course) {
setFill(course.claColor || '#409EFF');
ctx.fillRect(x, y, cellWidth, cellHeight);
setFill('#000000');
setFontSize(10);
setTextAlign('center');
setTextBaseline('middle');
let textY = y + 12;
ctx.fillText(course.startTime + '-' + course.endTime, x + cellWidth / 2, textY);
textY += 16;
ctx.fillText(course.courseName, x + cellWidth / 2, textY);
textY += 16;
if (course.staffName) {
ctx.fillText(course.staffName, x + cellWidth / 2, textY);
textY += 16;
}
if (course.roomName) {
ctx.fillText(course.roomName, x + cellWidth / 2, textY);
}
} else {
setFill('#ffffff');
ctx.fillRect(x, y, cellWidth, cellHeight);
}
setStroke('#cccccc');
ctx.strokeRect(x, y, cellWidth, cellHeight);
}
setStroke('#cccccc');
ctx.strokeRect(padding, y, rowHeaderWidth, cellHeight);
}
//
setStroke('#cccccc');
ctx.strokeRect(padding, padding, rowHeaderWidth, headerHeight);
ctx.strokeRect(padding, padding, rowHeaderWidth + cols * cellWidth, headerHeight + rows * cellHeight);
},
saveImage() {
if (!this.previewImagePath) {
uni.showToast({ title: '图片不存在', icon: 'none' });
return;
}
// #ifdef H5
try {
if (this.previewImagePath.startsWith('data:')) {
// base64 blob
const arr = this.previewImagePath.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const blob = new Blob([u8arr], { type: mime });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = '周课表.png';
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
} else {
const link = document.createElement('a');
link.download = '周课表.png';
link.href = this.previewImagePath;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
uni.showToast({ title: '下载成功' });
this.closePreview();
} catch (e) {
console.error(e);
uni.showToast({ title: '下载失败', icon: 'none' });
}
// #endif
// #ifndef H5
uni.saveImageToPhotosAlbum({
filePath: this.previewImagePath,
success: () => {
uni.showToast({ title: '保存成功' });
this.closePreview();
},
fail: (err) => {
console.error(err);
if (err.errMsg && err.errMsg.indexOf('auth deny') > -1) {
uni.showModal({
title: '提示',
content: '需要您授权保存到相册,是否前往设置?',
success: (res) => {
if (res.confirm) {
uni.openSetting();
}
}
});
} else {
uni.showToast({ title: '保存失败', icon: 'none' });
}
}
});
// #endif
}
,
closePreview() {
this.$refs.previewPopup.close();
//
// this.previewImagePath = '';
},
formatTime(timeStr) { formatTime(timeStr) {
// timeStr "HH:MM:SS" // timeStr "HH:MM:SS"
return timeStr.slice(0, 5); // 5 "HH:MM" return timeStr.slice(0, 5); // 5 "HH:MM"
@ -905,6 +1201,25 @@
height: calc(100vh - var(--window-top)); height: calc(100vh - var(--window-top));
/* #endif */ /* #endif */
} }
.preview-container {
background: #fff;
border-radius: 20rpx;
padding: 40rpx;
display: flex;
flex-direction: column;
align-items: center;
width: 90%;
max-width: 600rpx;
}
.preview-actions {
display: flex;
justify-content: space-around;
width: 100%;
margin-top: 30rpx;
button {
width: 40%;
}
}
.headcon{ .headcon{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -923,6 +1238,14 @@
font-size: 30rpx; font-size: 30rpx;
margin-left: 10rpx; margin-left: 10rpx;
} }
.save-btn {
display: flex;
align-items: center;
margin-right: 10rpx;
padding: 8rpx 16rpx;
background-color: rgba(0,169,154,0.1);
border-radius: 30rpx;
}
} }
} }
.tabtip{ .tabtip{

@ -213,7 +213,7 @@
myCache("signtype",'11'); myCache("signtype",'11');
// //
uni.switchTab({ uni.switchTab({
url: '/pages/user/user' url: '/pages/login/login'
}); });
} }
} }

@ -602,6 +602,9 @@
type: 'warning', type: 'warning',
duration: 2000 duration: 2000
}); });
uni.navigateTo({
url: `/pages/login/login`
});
} }
} }
}); });

Loading…
Cancel
Save