puyao il y a 3 semaines
Parent
commit
c4f0c60131

+ 179 - 0
components/FloatingActionButton/FloatingActionButton.vue

@@ -0,0 +1,179 @@
+<template>
+  <view class="fab-container" :style="positionStyle">
+    <!-- 菜单项 -->
+    <view class="fab-menu" v-if="isExpanded">
+      <view 
+        v-for="(item, index) in menuItems" 
+        :key="index"
+        class="menu-item"
+        @click="handleMenuItemClick(item, index)"
+      >
+        <text class="menu-item-text">{{ item.text }}</text>
+        <text class="menu-item-icon">{{ item.icon }}</text>
+      </view>
+    </view>
+    
+    <!-- 主悬浮按钮 -->
+    <view 
+      class="fab-main"
+      @click="toggleMenu"
+      :style="{ backgroundColor: bgColor }"
+    >
+      <text class="fab-icon" :class="{ rotated: isExpanded }">{{ icon }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  name: 'FloatingActionButton',
+  props: {
+    // 菜单项列表 [{ text: '分享', icon: '📤' }]
+    menuItems: {
+      type: Array,
+      default: () => []
+    },
+    // 主按钮图标
+    icon: {
+      type: String,
+      default: '+'
+    },
+    // 主按钮背景色
+    bgColor: {
+      type: String,
+      default: '#007AFF'
+    },
+    // 距离底部的距离(px)
+    bottom: {
+      type: Number,
+      default: 100
+    },
+    // 距离右侧的距离(px)
+    right: {
+      type: Number,
+      default: 30
+    }
+  },
+  data() {
+    return {
+      isExpanded: false
+    }
+  },
+  computed: {
+    positionStyle() {
+      return {
+        bottom: this.bottom + 'px',
+        right: this.right + 'px'
+      }
+    }
+  },
+  methods: {
+    toggleMenu() {
+      this.isExpanded = !this.isExpanded
+      this.$emit('fabClick', this.isExpanded)
+    },
+    handleMenuItemClick(item, index) {
+      this.$emit('menuItemClick', { item, index })
+      // 点击后自动收起菜单
+      this.isExpanded = false
+    },
+    // 手动展开
+    expand() {
+      this.isExpanded = true
+    },
+    // 手动收起
+    collapse() {
+      this.isExpanded = false
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.fab-container {
+  position: fixed;
+  z-index: 999;
+  display: flex;
+  flex-direction: column-reverse;
+  align-items: flex-end;
+}
+
+.fab-main {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.2);
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  &:active {
+    transform: scale(0.95);
+  }
+}
+
+.fab-icon {
+  font-size: 48rpx;
+  color: #fff;
+  transition: transform 0.3s ease;
+  
+  &.rotated {
+    transform: rotate(45deg);
+  }
+}
+
+.fab-menu {
+  display: flex;
+  flex-direction: column;
+  margin-bottom: 20rpx;
+  animation: slideUp 0.3s ease;
+}
+
+.menu-item {
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  margin-bottom: 20rpx;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  &:active {
+    transform: scale(0.95);
+  }
+}
+
+.menu-item-text {
+  background: rgba(0, 0, 0, 0.7);
+  color: #fff;
+  padding: 10rpx 20rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  white-space: nowrap;
+}
+
+.menu-item-icon {
+  width: 80rpx;
+  height: 80rpx;
+  background: #fff;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-left: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  font-size: 40rpx;
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(20rpx);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+</style>

+ 1 - 1
manifest.json

@@ -53,7 +53,7 @@
     /* 小程序特有相关 */
     "mp-weixin" : {
         // "appid" : "wx1de020b57c05a990",
-        "appid" : "wx06c66d8f7b8b01d0",
+        "appid" : "wxd2edd379beb6581b",
         "setting" : {
             "urlCheck" : false
         },

+ 65 - 0
pages_course/components/courseExpiration.vue

@@ -0,0 +1,65 @@
+<template>
+	<view class="container-body">
+		<text class="user" v-if="userId">#{{userId}}</text>
+		<image class="kfqrcode" v-if="qrcode" :src="qrcode" show-menu-by-longpress="true"></image>
+		<image v-else src="/static/course_expiration_img.png" mode="aspectFill"></image>
+		<u-parse class="title" :content="msg"></u-parse>
+		<view style="margin-bottom: 10vh;" v-show="code">状态码:{{code}}</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:['code','qrcode','msg','userId'],
+		data() {
+			return {
+			
+			}
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	.kfqrcode {
+		height: 460rpx;
+		width: 460rpx;
+	}
+	.container-body {
+		background-color: #fff;
+		position: fixed;
+		top: 0;
+		right: 0;
+		z-index: 9998;
+		justify-content: center;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 32rpx;
+		color: #757575;
+		line-height: 48rpx;
+		text-align: center;
+		height: 100vh;
+		width: 100vw;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		.title {
+			font-weight: bold;
+			font-size: 40rpx;
+			color: #1E2F67;
+			margin-bottom: 24rpx;
+			padding: 0 60rpx;
+		}
+		image {
+			width: 428rpx;
+			height: 360rpx;
+			margin-bottom: 30rpx;
+		}
+		.user {
+			position: absolute;
+			right: 24rpx;
+			top: 124rpx;
+			z-index: 9999;
+		}
+	}
+</style>

+ 446 - 0
pages_course/feedback.vue

@@ -0,0 +1,446 @@
+<template>
+	<view :style="{fontSize: fontSize(30)}">
+		<view class="header-nav" :style="{height: `calc(88rpx + ${statusBarHeight}px)`,paddingTop: statusBarHeight + 'px'}">
+			<view class="arrow-left" :style="{top: statusBarHeight + 'px'}" @click="goBack">返回</view>
+			<view class="header-title" :style="{height:menuButtonH+'px',lineHeight:menuButtonH+'px'}">投诉反馈</view>
+		</view>
+		<view class="container" :style="{paddingTop: `calc(88rpx + ${statusBarHeight}px)`}">
+			<view class="formbox" v-if="isLastChild==1">
+				<view class="formbox-title">{{ text }}</view>
+				<view class="form">
+					<u-form labelPosition="top" labelWidth='auto' :model="formdata" :rules="rules" ref="uForm" errorType="toast">
+						<u-form-item label=" " prop="complaintContent">
+							<u--textarea v-model="formdata.complaintContent" border="none" :clearable="true" placeholder="请填写反馈内容" count maxlength='200'></u--textarea>
+						</u-form-item>
+						<view class="box">
+							<u-form-item label="图片(最多9张)">
+								<view class="imgitem">
+									<u-upload
+										:fileList="fileList1"
+										@afterRead="afterRead"
+										@delete="deletePic"
+										name="1"
+										:maxCount="9"
+									></u-upload>
+								</view>
+							</u-form-item>
+						</view>
+					</u-form>
+				</view>
+				<view class="footer-btn">
+					<button class="submit-btn" @click="submit">提交</button>
+					<!-- <button class="submit-btn back-btn" @click="goBack">返回</button> -->
+				</view>
+			</view>
+			<view class="container" v-else>
+				<view class="list-item title">请选择反馈类型</view>
+				<view class="list-item" v-for="(item, index) in feedbackItems" :key="index"
+					@click="handleClick(item,index)">
+					<view>{{ item.complaintTypeName }}</view>
+					<uni-icons type="right" size="20" color="rgba(0,0,0,.3)" v-if="isLastChild==0"></uni-icons>
+				</view>
+				<view class="list-item" v-if="pageIndex!=0&&isLastChild==0" @click="goBack">
+					返回上一层
+				</view>
+			</view>
+		</view>
+		<yk-screenRecord></yk-screenRecord>
+	</view>
+</template>
+
+<script>
+	import ykScreenRecord from '@/components/yk-screenRecord/yk-screenRecord.vue';
+	import { mapGetters } from 'vuex';
+	import{ getTypeTree, complaintRecord,loginByMp } from "@/api/courseAuto.js"
+	export default {
+		components: {
+			ykScreenRecord
+		},
+		data() {
+			return {
+				statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
+				menuButtonH: 45,
+				pageIndex: 0,
+				list: [],
+				feedbackItems: [],
+				userId: '',
+				courseId: '',
+				videoId: '',
+				formdata: {
+					complaintContent: ""
+				},
+				rules: {
+					complaintContent:[{
+						required: true,
+						message: '投诉反馈内容不能为空',
+						trigger: ["change", "blur"]
+					}]
+				},
+				text: '',
+				templateId: 0,
+				user: {},
+				isLastChild: 0,
+				isLogin: false,
+				fileList1: [],
+			};
+		},
+		computed: {
+			...mapGetters(['coureLogin']),
+			fontSize() {
+				return size=>{
+					const value = uni.upx2px(size)
+					const scale = uni.getStorageSync('fontScale') || 1;
+					if(scale>1.5) {
+						return value * 1.5 + 'px';
+					} else if(scale<1){
+						return value + 'px';
+					}else {
+						return value * scale + 'px';
+					}
+				}
+			},
+		},
+		watch: {
+		    coureLogin: {
+		      immediate: true,          // 页面一进入就检查一次
+		      handler(val) {
+		        if (val == 2) {
+					console.log("AppToken失效,请重新登录")
+					this.isLogin = false
+					this.goLogin()
+		        }
+		      }
+		    }
+		},
+		onLoad(option) {
+			this.userId = option.userId || ''
+			this.courseId = option.courseId || ''
+			this.videoId = option.videoId || ''
+			this.utils.isLoginCourse().then(
+				res => {
+					if(res){
+						this.isLogin = true
+						this.getMenuButton()
+						this.getList()
+					} else{
+						this.isLogin = false
+						this.goLogin()
+					}
+				},
+				rej => {}
+			);
+		},
+		methods: {
+			getMenuButton(){
+				const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
+				this.menuButtonH = menuButtonInfo.height
+			},
+			goBack() {
+				// 返回上一层逻辑
+				if (this.pageIndex == 0) {
+					uni.navigateBack();
+				} else {
+					this.pageIndex--;
+					this.formdata = {
+						complaintContent: ""
+					}
+					if (this.isLastChild == 1) {
+						this.isLastChild = 0
+					} else {
+						if (this.pageIndex == 0) {
+							this.feedbackItems = this.list
+							this.templateId = 0
+						} else {
+							const list = this.findGrandparentOrAllData(this.list, this.templateId)
+							this.feedbackItems = list.childrenType
+							this.templateId = list.complaintTypeId
+						}
+					}
+				
+				}
+			},
+			findGrandparentOrAllData(data, targetId) {
+				// 递归函数,用于查找目标节点的父级节点
+				function findParent(node, targetId) {
+					if (!node || !node.childrenType) return null;
+			
+					for (let child of node.childrenType) {
+						if (child.complaintTypeId === targetId) {
+							return node; // 找到目标节点的父级节点
+						}
+			
+						const result = findParent(child, targetId); // 递归查找子节点
+						if (result) return result;
+					}
+			
+					return null;
+				}
+			
+				// 遍历顶层节点,查找目标节点的父级和祖父级节点
+				for (let root of data) {
+					if (root.complaintTypeId === targetId) {
+						return data; // 如果目标节点是顶层节点,返回所有数据
+					}
+			
+					const parent = findParent(root, targetId); // 查找目标节点的父级节点
+					if (parent) {
+						const grandparent = findParent(root, parent.complaintTypeId); // 查找父级节点的父级节点
+						return grandparent || data; // 如果找到祖父节点返回祖父节点,否则返回所有数据
+					}
+				}
+			
+				return data; // 如果没有找到目标节点,返回所有数据
+			},
+			handleClick(item,index) {
+				if (this.isLastChild == 1) return
+				if (this.pageIndex >= 0) {
+					this.pageIndex++
+					let children = this.feedbackItems[index].childrenType || [];
+					this.templateId = this.feedbackItems[index].complaintTypeId
+					this.formdata = {
+						complaintContent: ""
+					}
+					this.text = this.feedbackItems[index].complaintTypeName
+					if (children.length > 0) {
+						this.isLastChild = 0
+						this.feedbackItems = children
+						this.templateId = this.feedbackItems[0].complaintTypeId
+					} else {
+						this.isLastChild = 1
+						this.formdata = {
+							complaintContent: ""
+						}
+						setTimeout(() => {
+							this.$refs.uForm.setRules(this.rules)
+						}, 200)
+					}
+				}
+			},
+			getList(){
+				getTypeTree().then(res=>{
+					if(res.code == 200) {
+						this.list = res.data
+						this.pageIndex = 0
+						this.feedbackItems = this.list
+					}
+				})
+			},
+			submit() {
+				if(this.fileList1.some(item=>item.status == 'uploading')) {
+					uni.showToast({
+						title: '等待图片上传中',
+						icon: 'none'
+					})
+					return
+				}
+				var images=[];
+				this.fileList1.forEach(function(element) {
+					images.push(element.url)
+				});
+				this.$refs.uForm.validate().then(res => {
+					if (res) {
+						const param = {
+							userId: this.userId,
+							complaintTypeId: this.templateId,
+							complaintContent: this.formdata.complaintContent,
+							courseId: this.courseId,
+							videoId: this.videoId,
+							complaintUrl: images.toString()
+						}
+						complaintRecord(param).then(res=>{
+							uni.showModal({
+								title: '',
+								content: '我们已收到您的反馈,谢谢',
+								showCancel: false,
+								success: function (res) {
+									if (res.confirm) {
+										uni.navigateBack()
+									} else if (res.cancel) {
+										uni.navigateBack()
+									}
+								}
+							});
+						})
+					}
+				})
+			},
+			deletePic(event) {
+				this[`fileList${event.name}`].splice(event.index, 1)
+			},
+			async afterRead(event) {
+				// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+				let lists = [].concat(event.file)
+				let fileListLen = this[`fileList${event.name}`].length
+				lists.map((item) => {
+					this[`fileList${event.name}`].push({
+						...item,
+						status: 'uploading',
+						message: '上传中'
+					})
+				})
+				for (let i = 0; i < lists.length; i++) {
+					const result = await this.uploadFilePromise(lists[i].url)
+					let item = this[`fileList${event.name}`][fileListLen]
+					this[`fileList${event.name}`].splice(fileListLen, 1, Object.assign(item, {
+						status: 'success',
+						message: '',
+						url: result
+					}))
+					fileListLen++
+				}
+			},
+			uploadFilePromise(url) {
+				return new Promise((resolve, reject) => {
+					let a = uni.uploadFile({
+						url: 'https://userapp.jnmyunl.com/app/common/uploadOSS', // 仅为示例,非真实的接口地址
+						filePath: url,
+						name: 'file',
+						success: (res) => {
+							setTimeout(() => {
+								console.log(JSON.parse(res.data).url)
+								resolve(JSON.parse(res.data).url)
+							}, 1000)
+						}
+					});
+				})
+			},
+			goLogin() {
+				this.utils.getProvider().then(provider=>{
+					console.log('当前的环境商',provider)
+					if (!provider) {
+					  reject()
+					}
+					uni.login({
+						provider: provider,
+						success: async loginRes => {
+							console.log(loginRes)
+							uni.getUserInfo({
+							   provider: provider,
+							   success: (infoRes)=> {
+								    uni.showToast({
+										title: '处理中...',
+										icon: 'loading'
+								    });
+									loginByMp({code: loginRes.code,encryptedData:infoRes.encryptedData,iv:infoRes.iv,appId:"wx5ff68306954353f7"}).then(res=>{
+										 uni.hideLoading();
+										 if (res.code == 200) {
+											this.$store.commit('setCoureLogin', 1);
+											uni.setStorageSync('AppTokenmini_RTCourse', res.token);
+											uni.setStorageSync('auto_userInfo', JSON.stringify(res.user));
+											this.userId = res.user.userId || ''
+											this.isLogin = true
+											this.getMenuButton()
+											this.getList()
+										 } else {
+											uni.showToast({
+												title: res.msg,
+												icon: 'none'
+											});
+										 }
+									 }).catch(err=>{
+										uni.hideLoading();
+										uni.showToast({
+											icon:'none',
+											title: "登录失败,请重新登录",
+										});
+									});
+							   }
+							});
+						}
+					})
+				}).catch(err => {})
+			},
+		}
+	};
+</script>
+
+<style scoped lang="scss">
+	.container {
+		background-color: #fff;
+	}
+	.formbox-title {
+		padding-bottom: 30rpx;
+		border-bottom: 1px solid #f4f4f4;
+	}
+	.formbox {
+		border-top: 1px solid #f4f4f4;
+		padding: 30rpx;
+	}
+	.box {
+		padding-bottom: 24rpx;
+		border-top: 1px solid #f4f4f4;
+		.imgitem {
+			padding-top: 20rpx;
+		}
+	}
+	.footer-btn {
+		margin-top: 50rpx;
+	}
+	.submit-btn {
+		width: 50%;
+		height: 88rpx;
+		line-height: 88rpx;
+		text-align: center;
+		font-size: 30rpx;
+		font-family: PingFang SC;
+		color: #FFFFFF;
+		background: rgb(0,178,106);
+		border-radius: 16rpx;
+		border: 1rpx solid ;
+		margin-bottom: 30rpx;
+		&::after {
+			border: none;
+		}
+	}
+	.back-btn {
+		color: #bbb;
+		background: transparent;
+		border-radius: 16rpx;
+		border: 1rpx solid #999;
+	}
+	.header-nav {
+		height: 88rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		overflow: hidden;
+		background-color: #fff;
+		box-sizing: border-box;
+		width: 100%;
+		position: fixed;
+		top: 0;
+		left: 0;
+		.header-title {
+			flex: 1;
+			text-align: center;
+			overflow: hidden;
+			white-space: nowrap;
+			text-overflow: ellipsis;
+			font-family: PingFang SC,PingFang SC;
+			font-weight: 500;
+			font-size: 15px;
+			color: #000;
+			box-sizing: border-box;
+		}
+	}
+	.arrow-left {
+		position: absolute;
+		left: 24rpx;
+		height: 88rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		overflow: hidden;
+	}
+	.list-item {
+		background-color: #fff;
+		padding: 24rpx;
+		border-bottom: 1rpx solid #f4f4f4;
+		font-size: 15px;
+		color: #333;
+	}
+
+	.title {
+		background-color: #f4f4f4;
+	}
+</style>

BIN
static/course_expiration_img.png


BIN
static/warning.png


+ 5 - 5
store/index.js

@@ -18,14 +18,14 @@ export default new Vuex.Store({
 	 //红包领取规则:
 	 //获取惠选的图片,logo加s
 	 answerType: 1, //红包领取规则 0:完课且最后一分钟(第二次无需最后一分钟), 1:按完课百分比答题领红包
-	 isSpare:0, // 0,主要小程序,1:备选
+	 isSpare:1, // 0,主要小程序,1:备选
 	 uploadFile: 'https://userapp.cqsft.vip',
 	imgpath: 'https://kntobs.jnmyunl.com',//惠选商城图片请求地址
 	// https://kntobs.jnmyunl.com/shop  惠选商城图片链接
-	logoname:'德信严选',
-	appid:'wx1de020b57c05a990',//臻选
-	// appid:'wxd2edd379beb6581b',//惠选
-	// appid:'wx06c66d8f7b8b01d0',//其他
+	logoname:'康年惠选',
+	// appid:'wx1de020b57c05a990',//臻选
+	appid:'wxd2edd379beb6581b',//惠选
+	// appid:'wx3d99227bd098ba02',//其他
   },
   mutations: {
     setCount(state, value) {