hookehuyr

chore: 将品牌名称从“美乐爱觉”更新为“生命力教育联盟”

更新所有页面、组件和文档中的品牌名称,包括用户协议、页面标题、分享标题、海报文案等,以反映品牌名称变更
......@@ -12,35 +12,49 @@
position="bottom"
:style="{ height: '100%' }"
>
<div class="flex flex-col h-full relative">
<div class="sticky top-0 flex justify-between items-center p-4 border-b border-gray-100 bg-white z-10">
<h3 class="font-medium text-lg">{{ title }}</h3>
<div class="relative flex h-full flex-col">
<div
class="sticky top-0 z-10 flex items-center justify-between border-b border-gray-100 bg-white p-4"
>
<h3 class="text-lg font-medium">{{ title }}</h3>
<van-icon name="cross" @click="$emit('update:show', false)" class="text-gray-500" />
</div>
<div class="flex-1 overflow-y-auto p-4 pt-0">
<div v-if="type === 'terms'" class="space-y-4 text-gray-600">
<p>欢迎使用美乐爱觉教育!在使用我们的服务之前,请仔细阅读以下用户协议。</p>
<p>欢迎使用生命力教育联盟教育!在使用我们的服务之前,请仔细阅读以下用户协议。</p>
<h5 class="font-medium text-gray-800">1. 服务内容</h5>
<p>美乐爱觉教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。</p>
<p>
生命力教育联盟教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。
</p>
<h5 class="font-medium text-gray-800">2. 用户责任</h5>
<p>用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。</p>
<p>
用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。
</p>
<h5 class="font-medium text-gray-800">3. 知识产权</h5>
<p>本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。</p>
<p>
本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。
</p>
<h5 class="font-medium text-gray-800">4. 免责声明</h5>
<p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p>
<h5 class="font-medium text-gray-800">1. 服务内容</h5>
<p>美乐爱觉教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。</p>
<p>
生命力教育联盟教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。
</p>
<h5 class="font-medium text-gray-800">2. 用户责任</h5>
<p>用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。</p>
<p>
用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。
</p>
<h5 class="font-medium text-gray-800">3. 知识产权</h5>
<p>本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。</p>
<p>
本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。
</p>
<h5 class="font-medium text-gray-800">4. 免责声明</h5>
<p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p>
......@@ -50,10 +64,14 @@
<p>我们重视您的隐私保护。本隐私政策说明我们如何收集、使用和保护您的个人信息。</p>
<h5 class="font-medium text-gray-800">1. 信息收集</h5>
<p>我们收集的信息包括但不限于:姓名、联系方式、位置信息等。这些信息用于提供更好的服务体验。</p>
<p>
我们收集的信息包括但不限于:姓名、联系方式、位置信息等。这些信息用于提供更好的服务体验。
</p>
<h5 class="font-medium text-gray-800">2. 信息使用</h5>
<p>我们承诺对您的个人信息进行严格保密,不会将其出售、出租或以其他方式泄露给任何第三方。</p>
<p>
我们承诺对您的个人信息进行严格保密,不会将其出售、出租或以其他方式泄露给任何第三方。
</p>
<h5 class="font-medium text-gray-800">3. 信息安全</h5>
<p>我们采用行业标准的安全措施保护您的个人信息,防止未经授权的访问、使用或泄露。</p>
......@@ -72,19 +90,19 @@ defineProps({
show: {
type: Boolean,
required: true,
default: false
default: false,
},
/** 协议类型: 'terms' | 'privacy' */
type: {
type: String,
required: true,
validator: (value) => ['terms', 'privacy'].includes(value)
validator: value => ['terms', 'privacy'].includes(value),
},
/** 弹窗标题 */
title: {
type: String,
required: true
}
required: true,
},
})
defineEmits(['update:show'])
......
......@@ -9,69 +9,81 @@
* @Description: 用户协议组件
-->
<template>
<van-popup
v-model:show="show"
round
position="bottom"
:style="{ height: '90%' }"
teleport="body"
>
<van-popup v-model:show="show" round position="bottom" :style="{ height: '90%' }" teleport="body">
<div class="p-4">
<div class="text-xl font-bold text-center mb-4">美乐爱觉宇宙用户协议</div>
<div class="agreement-content overflow-y-auto h-[calc(100vh*0.8-120px)] px-2">
<h2 class="text-lg font-semibold mb-3">1. 协议的范围</h2>
<p class="mb-4 text-gray-700">欢迎您使用美乐爱觉宇宙平台服务!为使用美乐爱觉宇宙平台服务,您应当阅读并遵守本《用户协议》。请您务必审慎阅读、充分理解各条款内容。</p>
<div class="mb-4 text-center text-xl font-bold">生命力教育联盟宇宙用户协议</div>
<div class="agreement-content h-[calc(100vh*0.8-120px)] overflow-y-auto px-2">
<h2 class="mb-3 text-lg font-semibold">1. 协议的范围</h2>
<p class="mb-4 text-gray-700">
欢迎您使用生命力教育联盟宇宙平台服务!为使用生命力教育联盟宇宙平台服务,您应当阅读并遵守本《用户协议》。请您务必审慎阅读、充分理解各条款内容。
</p>
<h2 class="text-lg font-semibold mb-3">2. 账号注册</h2>
<p class="mb-4 text-gray-700">您在使用本服务前需要注册一个美乐爱觉宇宙账号。美乐爱觉宇宙账号应当使用手机号码绑定注册,请您使用尚未与美乐爱觉宇宙账号绑定的手机号码,以及未被平台根据本协议封禁的手机号码注册。</p>
<h2 class="mb-3 text-lg font-semibold">2. 账号注册</h2>
<p class="mb-4 text-gray-700">
您在使用本服务前需要注册一个生命力教育联盟宇宙账号。生命力教育联盟宇宙账号应当使用手机号码绑定注册,请您使用尚未与生命力教育联盟宇宙账号绑定的手机号码,以及未被平台根据本协议封禁的手机号码注册。
</p>
<h2 class="text-lg font-semibold mb-3">3. 用户个人信息保护</h2>
<p class="mb-4 text-gray-700">我们非常重视用户个人信息的保护,保护用户个人信息是我们的基本原则之一。我们将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可我们不会向第三方公开、透露用户个人信息。</p>
<h2 class="mb-3 text-lg font-semibold">3. 用户个人信息保护</h2>
<p class="mb-4 text-gray-700">
我们非常重视用户个人信息的保护,保护用户个人信息是我们的基本原则之一。我们将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可我们不会向第三方公开、透露用户个人信息。
</p>
<h2 class="text-lg font-semibold mb-3">4. 内容规范</h2>
<p class="mb-4 text-gray-700">您在使用本服务时需要遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性等七条底线。</p>
<h2 class="mb-3 text-lg font-semibold">4. 内容规范</h2>
<p class="mb-4 text-gray-700">
您在使用本服务时需要遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性等七条底线。
</p>
<h2 class="text-lg font-semibold mb-3">5. 知识产权</h2>
<p class="mb-4 text-gray-700">美乐爱觉宇宙平台所包含的全部智力成果,包括但不限于平台内容、平台设计、源代码等,均属于平台所有。未经平台许可,任何人不得擅自使用。</p>
<h2 class="mb-3 text-lg font-semibold">5. 知识产权</h2>
<p class="mb-4 text-gray-700">
生命力教育联盟宇宙平台所包含的全部智力成果,包括但不限于平台内容、平台设计、源代码等,均属于平台所有。未经平台许可,任何人不得擅自使用。
</p>
<h2 class="text-lg font-semibold mb-3">6. 服务的变更、中断和终止</h2>
<p class="mb-4 text-gray-700">我们可能会对服务内容进行变更,也可能会中断、中止或终止服务。对于付费服务,我们会在变更前通知您,并向您提供退款等必要的补偿。</p>
<h2 class="mb-3 text-lg font-semibold">6. 服务的变更、中断和终止</h2>
<p class="mb-4 text-gray-700">
我们可能会对服务内容进行变更,也可能会中断、中止或终止服务。对于付费服务,我们会在变更前通知您,并向您提供退款等必要的补偿。
</p>
<h2 class="text-lg font-semibold mb-3">7. 违约处理</h2>
<p class="mb-4 text-gray-700">如果您违反本协议约定,我们有权视情况采取预先警示、限制或禁止使用全部或部分服务功能、封禁账号等措施。</p>
<h2 class="mb-3 text-lg font-semibold">7. 违约处理</h2>
<p class="mb-4 text-gray-700">
如果您违反本协议约定,我们有权视情况采取预先警示、限制或禁止使用全部或部分服务功能、封禁账号等措施。
</p>
<h2 class="text-lg font-semibold mb-3">8. 其他条款</h2>
<p class="mb-4 text-gray-700">本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。如果本协议中任何一条被视为废止、无效或不可执行,应视为可分的且并不影响任何其余条款的有效性和可执行性。</p>
<h2 class="mb-3 text-lg font-semibold">8. 其他条款</h2>
<p class="mb-4 text-gray-700">
本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。如果本协议中任何一条被视为废止、无效或不可执行,应视为可分的且并不影响任何其余条款的有效性和可执行性。
</p>
</div>
<div class="flex justify-center mt-4">
<van-button round type="primary" color="#4CAF50" block @click="handleClose">我已阅读并同意</van-button>
<div class="mt-4 flex justify-center">
<van-button round type="primary" color="#4CAF50" block @click="handleClose"
>我已阅读并同意</van-button
>
</div>
</div>
</van-popup>
</template>
<script setup>
import { ref, defineExpose } from 'vue';
import { ref, defineExpose } from 'vue'
const show = ref(false);
const show = ref(false)
/**
* @description 关闭协议弹窗
*/
const handleClose = () => {
show.value = false;
};
show.value = false
}
/**
* @description 打开协议弹窗
*/
const openAgreement = () => {
show.value = true;
};
show.value = true
}
defineExpose({
openAgreement
});
openAgreement,
})
</script>
<style lang="less" scoped>
......
<template>
<div class="recall-poster-container w-full max-w-[340px] relative select-none shrink-0 my-auto mx-auto">
<!-- 最终生成的海报图片展示区域 -->
<div v-if="posterImgSrc" class="relative w-full fade-in">
<img :src="posterImgSrc" class="w-full h-auto rounded-2xl shadow-2xl block" alt="分享海报" />
<div class="text-white/80 text-center text-xs mt-4">长按图片保存</div>
</div>
<!-- 生成中/加载中占位 -->
<div v-else
class="w-full h-[62vh] min-h-[400px] bg-white/10 backdrop-blur-sm rounded-2xl flex flex-col items-center justify-center text-white/80">
<van-loading type="spinner" color="#ffffff" size="32px" />
<div class="mt-4 text-sm font-medium">海报生成中...</div>
</div>
<!-- Canvas (隐藏) -->
<canvas ref="canvasRef" class="hidden"></canvas>
<div
class="recall-poster-container relative mx-auto my-auto w-full max-w-[340px] shrink-0 select-none"
>
<!-- 最终生成的海报图片展示区域 -->
<div v-if="posterImgSrc" class="fade-in relative w-full">
<img :src="posterImgSrc" class="block h-auto w-full rounded-2xl shadow-2xl" alt="分享海报" />
<div class="mt-4 text-center text-xs text-white/80">长按图片保存</div>
</div>
<!-- 生成中/加载中占位 -->
<div
v-else
class="flex h-[62vh] min-h-[400px] w-full flex-col items-center justify-center rounded-2xl bg-white/10 text-white/80 backdrop-blur-sm"
>
<van-loading type="spinner" color="#ffffff" size="32px" />
<div class="mt-4 text-sm font-medium">海报生成中...</div>
</div>
<!-- Canvas (隐藏) -->
<canvas ref="canvasRef" class="hidden"></canvas>
</div>
</template>
<script setup>
......@@ -24,67 +28,66 @@ import { showToast } from 'vant'
// import request from '@/utils/axios' // 移除 axios 依赖,改用 fetch
const props = defineProps({
/** 海报背景图片 URL */
bgUrl: {
type: String,
required: true
},
/** 海报标题 */
title: {
type: String,
default: ''
},
/** Logo 图片 URL */
logoUrl: {
type: String,
default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png'
},
/** 二维码图片 URL */
qrUrl: {
type: String,
default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/%E4%BA%8C%E7%BB%B4%E7%A0%81@2x.png'
}
/** 海报背景图片 URL */
bgUrl: {
type: String,
required: true,
},
/** 海报标题 */
title: {
type: String,
default: '',
},
/** Logo 图片 URL */
logoUrl: {
type: String,
default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png',
},
/** 二维码图片 URL */
qrUrl: {
type: String,
default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/%E4%BA%8C%E7%BB%B4%E7%A0%81@2x.png',
},
})
const canvasRef = ref(null)
const posterImgSrc = ref('')
// 工具函数:加载图片
const loadImage = (src) => {
return new Promise((resolve, reject) => {
if (!src) {
reject(new Error('Image source is empty'))
return
}
const img = new Image()
// 处理跨域,Blob URL 不需要
if (!src.startsWith('blob:') && !src.startsWith('data:')) {
img.crossOrigin = 'anonymous'
}
img.onload = () => resolve(img)
img.onerror = (e) => {
console.error('Failed to load image:', src)
// 图片加载失败不应该阻断流程,返回 null 或者透明图占位
// 这里resolve null,绘制时跳过
resolve(null)
}
img.src = src
})
}
const loadImage = src =>
new Promise((resolve, reject) => {
if (!src) {
reject(new Error('Image source is empty'))
return
}
const img = new Image()
// 处理跨域,Blob URL 不需要
if (!src.startsWith('blob:') && !src.startsWith('data:')) {
img.crossOrigin = 'anonymous'
}
img.onload = () => resolve(img)
img.onerror = e => {
console.error('Failed to load image:', src)
// 图片加载失败不应该阻断流程,返回 null 或者透明图占位
// 这里resolve null,绘制时跳过
resolve(null)
}
img.src = src
})
// 工具函数:绘制圆角矩形
const drawRoundedRect = (ctx, x, y, width, height, radius) => {
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius)
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
ctx.lineTo(x + radius, y + height)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
ctx.closePath()
ctx.beginPath()
ctx.moveTo(x + radius, y)
ctx.lineTo(x + width - radius, y)
ctx.quadraticCurveTo(x + width, y, x + width, y + radius)
ctx.lineTo(x + width, y + height - radius)
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height)
ctx.lineTo(x + radius, y + height)
ctx.quadraticCurveTo(x, y + height, x, y + height - radius)
ctx.lineTo(x, y + radius)
ctx.quadraticCurveTo(x, y, x + radius, y)
ctx.closePath()
}
// 工具函数:绘制多行文本
......@@ -99,293 +102,297 @@ const drawRoundedRect = (ctx, x, y, width, height, radius) => {
* @param {number} maxLines - 最大行数
*/
const wrapText = (ctx, text, x, y, maxWidth, lineHeight, maxLines) => {
const words = text.split('') // 中文按字分割
let line = ''
let lineCount = 0
let currentY = y
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n]
const metrics = ctx.measureText(testLine)
const testWidth = metrics.width
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, currentY)
line = words[n]
currentY += lineHeight
lineCount++
if (maxLines && lineCount >= maxLines) {
// 超过最大行数,最后一行加省略号(简化处理,暂不精确计算省略号位置)
return
}
} else {
line = testLine
}
const words = text.split('') // 中文按字分割
let line = ''
let lineCount = 0
let currentY = y
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n]
const metrics = ctx.measureText(testLine)
const testWidth = metrics.width
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, currentY)
line = words[n]
currentY += lineHeight
lineCount++
if (maxLines && lineCount >= maxLines) {
// 超过最大行数,最后一行加省略号(简化处理,暂不精确计算省略号位置)
return
}
} else {
line = testLine
}
ctx.fillText(line, x, currentY)
}
ctx.fillText(line, x, currentY)
}
// 工具函数:绘制竖排文字
const drawVerticalText = (ctx, text, x, y, fontSize, letterSpacing) => {
const chars = text.split('')
let currentY = y
ctx.font = `500 ${fontSize}px sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
chars.forEach(char => {
ctx.fillText(char, x, currentY)
currentY += fontSize + letterSpacing
})
const chars = text.split('')
let currentY = y
ctx.font = `500 ${fontSize}px sans-serif`
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
chars.forEach(char => {
ctx.fillText(char, x, currentY)
currentY += fontSize + letterSpacing
})
}
// 核心生成逻辑:Canvas 绘制
const generatePoster = async () => {
posterImgSrc.value = ''
// 确保 Canvas 元素存在
await nextTick()
const canvas = canvasRef.value
if (!canvas) return
try {
// 1. 准备画布尺寸 (2倍图)
const scale = 2
const width = 375 * scale
// 图片区域高度缩小,从 600 -> 450,保持较好比例
const imgAreaHeight = 450 * scale
const infoAreaHeight = 130 * scale
const height = imgAreaHeight + infoAreaHeight
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
// 设置白色背景
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, width, height)
// 处理 API 形式的二维码 URL (如 /admin/?m=srv...)
// 如果是相对路径或特定API,使用 fetch 获取 blob 以通过鉴权并避免跨域/拦截器问题
let qrUrlToLoad = props.qrUrl
let qrBlobUrl = null
// 兼容完整 URL 和相对路径,只要包含特定特征或是相对路径
if (qrUrlToLoad && (qrUrlToLoad.startsWith('/') || qrUrlToLoad.includes('m=srv') || qrUrlToLoad.includes('http'))) {
try {
// 使用 fetch 替代 axios,避免拦截器自动添加 header 导致 CORS 预检失败
const res = await fetch(qrUrlToLoad)
if (res.ok) {
const blob = await res.blob()
qrBlobUrl = URL.createObjectURL(blob)
qrUrlToLoad = qrBlobUrl
}
} catch (err) {
console.warn('QR Code API fetch failed, falling back to direct load', err)
}
posterImgSrc.value = ''
// 确保 Canvas 元素存在
await nextTick()
const canvas = canvasRef.value
if (!canvas) return
try {
// 1. 准备画布尺寸 (2倍图)
const scale = 2
const width = 375 * scale
// 图片区域高度缩小,从 600 -> 450,保持较好比例
const imgAreaHeight = 450 * scale
const infoAreaHeight = 130 * scale
const height = imgAreaHeight + infoAreaHeight
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
// 设置白色背景
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, width, height)
// 处理 API 形式的二维码 URL (如 /admin/?m=srv...)
// 如果是相对路径或特定API,使用 fetch 获取 blob 以通过鉴权并避免跨域/拦截器问题
let qrUrlToLoad = props.qrUrl
let qrBlobUrl = null
// 兼容完整 URL 和相对路径,只要包含特定特征或是相对路径
if (
qrUrlToLoad &&
(qrUrlToLoad.startsWith('/') || qrUrlToLoad.includes('m=srv') || qrUrlToLoad.includes('http'))
) {
try {
// 使用 fetch 替代 axios,避免拦截器自动添加 header 导致 CORS 预检失败
const res = await fetch(qrUrlToLoad)
if (res.ok) {
const blob = await res.blob()
qrBlobUrl = URL.createObjectURL(blob)
qrUrlToLoad = qrBlobUrl
}
} catch (err) {
console.warn('QR Code API fetch failed, falling back to direct load', err)
}
}
// 2. 并行加载所有图片资源
const [bgImg, logoImg, qrImg] = await Promise.all([
loadImage(props.bgUrl),
loadImage(props.logoUrl),
loadImage(qrUrlToLoad)
])
// 清理 Blob URL
if (qrBlobUrl) {
URL.revokeObjectURL(qrBlobUrl)
}
// 2. 并行加载所有图片资源
const [bgImg, logoImg, qrImg] = await Promise.all([
loadImage(props.bgUrl),
loadImage(props.logoUrl),
loadImage(qrUrlToLoad),
])
// 3. 绘制背景图 (Object-Cover 效果)
if (bgImg) {
// 计算裁剪
const imgRatio = bgImg.width / bgImg.height
const canvasRatio = width / imgAreaHeight
let sx, sy, sWidth, sHeight
if (imgRatio > canvasRatio) {
// 图片更宽,裁左右
sHeight = bgImg.height
sWidth = sHeight * canvasRatio
sx = (bgImg.width - sWidth) / 2
sy = 0
} else {
// 图片更高,裁上下
sWidth = bgImg.width
sHeight = sWidth / canvasRatio
sx = 0
sy = (bgImg.height - sHeight) / 2
}
ctx.drawImage(bgImg, sx, sy, sWidth, sHeight, 0, 0, width, imgAreaHeight)
}
// 清理 Blob URL
if (qrBlobUrl) {
URL.revokeObjectURL(qrBlobUrl)
}
// 4. 绘制 Logo (左上角)
if (logoImg) {
const logoW = 96 * scale // w-24 = 96px
const logoRatio = logoImg.width / logoImg.height
const logoH = logoW / logoRatio
const logoX = 24 * scale // left-6
const logoY = 24 * scale // top-6
// 添加阴影
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)'
ctx.shadowBlur = 4 * scale
ctx.shadowOffsetY = 2 * scale
ctx.drawImage(logoImg, logoX, logoY, logoW, logoH)
ctx.shadowColor = 'transparent' // 重置阴影
}
// 3. 绘制背景图 (Object-Cover 效果)
if (bgImg) {
// 计算裁剪
const imgRatio = bgImg.width / bgImg.height
const canvasRatio = width / imgAreaHeight
let sx, sy, sWidth, sHeight
if (imgRatio > canvasRatio) {
// 图片更宽,裁左右
sHeight = bgImg.height
sWidth = sHeight * canvasRatio
sx = (bgImg.width - sWidth) / 2
sy = 0
} else {
// 图片更高,裁上下
sWidth = bgImg.width
sHeight = sWidth / canvasRatio
sx = 0
sy = (bgImg.height - sHeight) / 2
}
ctx.drawImage(bgImg, sx, sy, sWidth, sHeight, 0, 0, width, imgAreaHeight)
}
// 5. 绘制竖排文字 (右下角)
// 位置: right-5 (20px), bottom-6 (24px) relative to imgArea
const textRightMargin = 20 * scale
const textBottomMargin = 24 * scale
const fontSize = 13 * scale
const letterSpacing = fontSize * 0.5 // tracking-[0.5em]
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'
// Shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
ctx.shadowBlur = 2 * scale
ctx.shadowOffsetY = 1 * scale
// Column 1: "每一段成长故事" (左边那列,离右边远一点)
// Column 2: "见证我在美乐爱觉宇宙的" (右边那列)
const colRightX = width - textRightMargin - (fontSize / 2)
const colLeftX = colRightX - fontSize - 12 * scale // gap-3 = 12px
const textRight = "见证我在美乐爱觉宇宙的"
const textLeft = "每一段成长故事"
// 计算文本对齐
// 目标:整个文本块的底部与 bottom-6 对齐
// 右列(第一句)作为基准
// 左列(第二句)相对于右列下移 48px (mt-12)
const charH = fontSize + letterSpacing
const hRight = textRight.length * charH - letterSpacing
const hLeft = textLeft.length * charH - letterSpacing
const offset = 48 * scale // mt-12
const maxBottom = imgAreaHeight - textBottomMargin
// 计算起始 Y 坐标,使得最底部的点不超过 maxBottom
const heightDiff = Math.max(hRight, offset + hLeft)
const tRight = maxBottom - heightDiff
const tLeft = tRight + offset
// 设置字体
ctx.font = `500 ${fontSize}px sans-serif`
// 内部绘制函数
const drawVert = (txt, x, startY) => {
const chars = txt.split('')
let cy = startY + fontSize / 2
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
chars.forEach(char => {
ctx.fillText(char, x, cy)
cy += fontSize + letterSpacing
})
}
// 4. 绘制 Logo (左上角)
if (logoImg) {
const logoW = 96 * scale // w-24 = 96px
const logoRatio = logoImg.width / logoImg.height
const logoH = logoW / logoRatio
const logoX = 24 * scale // left-6
const logoY = 24 * scale // top-6
// 添加阴影
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)'
ctx.shadowBlur = 4 * scale
ctx.shadowOffsetY = 2 * scale
ctx.drawImage(logoImg, logoX, logoY, logoW, logoH)
ctx.shadowColor = 'transparent' // 重置阴影
}
drawVert(textRight, colRightX, tRight)
drawVert(textLeft, colLeftX, tLeft)
ctx.shadowColor = 'transparent'
// 6. 绘制 Info Area 内容
// 坐标参考
const infoY = imgAreaHeight
// 6.1 Title
// Padding: p-5 (20px). Left side.
// pr-4 (16px) for title container.
// Title Width = Total Width - Padding Left - Padding Right - QR Section Width
// QR Section: w-[72px] + margins?
// DOM: justify-between.
// QR Section is shrink-0.
// QR Image: 72px. Text below.
// Let's reserve 100px width for QR section on the right.
const padding = 20 * scale
const titleX = padding
const titleY = infoY + padding
const qrSectionW = 85 * scale // approx
const titleMaxW = width - padding - qrSectionW - (10 * scale) // extra gap
ctx.fillStyle = '#0052D9'
ctx.font = `bold ${15 * scale}px sans-serif`
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
// Line height: leading-relaxed (approx 1.6?)
const lineHeight = 15 * scale * 1.6
wrapText(ctx, props.title, titleX, titleY, titleMaxW, lineHeight, 4)
// 6.2 QR Code
if (qrImg) {
const qrSize = 72 * scale
// Center in the right section
// Section starts at width - padding - qrSectionW?
// Actually DOM is flex justify-between.
// QR is at the very right (minus padding).
const qrX = width - padding - qrSize + (4 * scale) // slight adjustment
const qrY = infoY + padding
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize)
// 6.3 QR Text
// text-[10px], scale-90. Effective size 9px.
// text-[#666]
const smallTextSize = 10 * scale * 0.9
ctx.fillStyle = '#666666'
ctx.font = `${smallTextSize}px sans-serif`
ctx.textAlign = 'center'
const textCenterX = qrX + qrSize / 2
const textStartY = qrY + qrSize + (8 * scale) // mb-2 is for img
ctx.fillText("跟我一起加入", textCenterX, textStartY)
ctx.fillText("美乐爱觉宇宙吧", textCenterX, textStartY + smallTextSize * 1.4)
}
// 5. 绘制竖排文字 (右下角)
// 位置: right-5 (20px), bottom-6 (24px) relative to imgArea
const textRightMargin = 20 * scale
const textBottomMargin = 24 * scale
const fontSize = 13 * scale
const letterSpacing = fontSize * 0.5 // tracking-[0.5em]
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'
// Shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
ctx.shadowBlur = 2 * scale
ctx.shadowOffsetY = 1 * scale
// Column 1: "每一段成长故事" (左边那列,离右边远一点)
// Column 2: "见证我在生命力教育联盟宇宙的" (右边那列)
const colRightX = width - textRightMargin - fontSize / 2
const colLeftX = colRightX - fontSize - 12 * scale // gap-3 = 12px
const textRight = '见证我在生命力教育联盟宇宙的'
const textLeft = '每一段成长故事'
// 计算文本对齐
// 目标:整个文本块的底部与 bottom-6 对齐
// 右列(第一句)作为基准
// 左列(第二句)相对于右列下移 48px (mt-12)
const charH = fontSize + letterSpacing
const hRight = textRight.length * charH - letterSpacing
const hLeft = textLeft.length * charH - letterSpacing
const offset = 48 * scale // mt-12
const maxBottom = imgAreaHeight - textBottomMargin
// 计算起始 Y 坐标,使得最底部的点不超过 maxBottom
const heightDiff = Math.max(hRight, offset + hLeft)
const tRight = maxBottom - heightDiff
const tLeft = tRight + offset
// 设置字体
ctx.font = `500 ${fontSize}px sans-serif`
// 7. 导出图片
posterImgSrc.value = canvas.toDataURL('image/png')
// 内部绘制函数
const drawVert = (txt, x, startY) => {
const chars = txt.split('')
let cy = startY + fontSize / 2
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
chars.forEach(char => {
ctx.fillText(char, x, cy)
cy += fontSize + letterSpacing
})
}
} catch (error) {
console.error('Canvas poster generation failed:', error)
showToast('生成失败,请重试')
drawVert(textRight, colRightX, tRight)
drawVert(textLeft, colLeftX, tLeft)
ctx.shadowColor = 'transparent'
// 6. 绘制 Info Area 内容
// 坐标参考
const infoY = imgAreaHeight
// 6.1 Title
// Padding: p-5 (20px). Left side.
// pr-4 (16px) for title container.
// Title Width = Total Width - Padding Left - Padding Right - QR Section Width
// QR Section: w-[72px] + margins?
// DOM: justify-between.
// QR Section is shrink-0.
// QR Image: 72px. Text below.
// Let's reserve 100px width for QR section on the right.
const padding = 20 * scale
const titleX = padding
const titleY = infoY + padding
const qrSectionW = 85 * scale // approx
const titleMaxW = width - padding - qrSectionW - 10 * scale // extra gap
ctx.fillStyle = '#0052D9'
ctx.font = `bold ${15 * scale}px sans-serif`
ctx.textAlign = 'left'
ctx.textBaseline = 'top'
// Line height: leading-relaxed (approx 1.6?)
const lineHeight = 15 * scale * 1.6
wrapText(ctx, props.title, titleX, titleY, titleMaxW, lineHeight, 4)
// 6.2 QR Code
if (qrImg) {
const qrSize = 72 * scale
// Center in the right section
// Section starts at width - padding - qrSectionW?
// Actually DOM is flex justify-between.
// QR is at the very right (minus padding).
const qrX = width - padding - qrSize + 4 * scale // slight adjustment
const qrY = infoY + padding
ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize)
// 6.3 QR Text
// text-[10px], scale-90. Effective size 9px.
// text-[#666]
const smallTextSize = 10 * scale * 0.9
ctx.fillStyle = '#666666'
ctx.font = `${smallTextSize}px sans-serif`
ctx.textAlign = 'center'
const textCenterX = qrX + qrSize / 2
const textStartY = qrY + qrSize + 8 * scale // mb-2 is for img
ctx.fillText('跟我一起加入', textCenterX, textStartY)
ctx.fillText('生命力教育联盟宇宙吧', textCenterX, textStartY + smallTextSize * 1.4)
}
// 7. 导出图片
posterImgSrc.value = canvas.toDataURL('image/png')
} catch (error) {
console.error('Canvas poster generation failed:', error)
showToast('生成失败,请重试')
}
}
watch(() => [props.bgUrl, props.qrUrl], () => {
watch(
() => [props.bgUrl, props.qrUrl],
() => {
generatePoster()
})
}
)
onMounted(() => {
// 稍微延时确保字体等资源就绪(虽然canvas不强依赖DOM渲染,但字体加载是全局的)
setTimeout(generatePoster, 500)
// 稍微延时确保字体等资源就绪(虽然canvas不强依赖DOM渲染,但字体加载是全局的)
setTimeout(generatePoster, 500)
})
defineExpose({
generatePoster
generatePoster,
})
</script>
<style scoped>
.fade-in {
animation: fadeIn 0.5s ease-in-out;
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
from {
opacity: 0;
}
to {
opacity: 1;
}
to {
opacity: 1;
}
}
</style>
......
......@@ -8,12 +8,12 @@
<template>
<div class="welcome-content">
<!-- 标题区域 -->
<div class="mt-20 flex flex-col items-center z-10 w-full px-8">
<img :src="titleImg" class="w-full max-w-[300px] mb-4 object-contain" alt="美乐爱觉" />
<div class="z-10 mt-20 flex w-full flex-col items-center px-8">
<img :src="titleImg" class="mb-4 w-full max-w-[300px] object-contain" alt="生命力教育联盟" />
</div>
<!-- 功能入口区域 - 水平布局,自动推到底部 -->
<div class="mt-auto entry-orbit">
<div class="entry-orbit mt-auto">
<div class="orbit-entries">
<WelcomeEntryItem
v-for="entry in entries"
......@@ -39,7 +39,7 @@ const entries = ref(welcomeEntries)
// 导入标题图片
const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title007@2x.png'
const handleEntryClick = (entry) => {
const handleEntryClick = entry => {
if (entry.isExternal) {
// 外部链接:获取用户ID并拼接
const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}')
......@@ -103,7 +103,8 @@ const handleEntryClick = (entry) => {
// 动画定义
@keyframes float {
0%, 100% {
0%,
100% {
transform: translateY(0);
}
50% {
......
......@@ -5,7 +5,7 @@
* @FilePath: /mlaj/src/composables/useShare.js
* @Description: 微信分享相关逻辑
*/
import wx from 'weixin-js-sdk';
import wx from 'weixin-js-sdk'
/**
* @function normalize_image_url
......@@ -14,15 +14,15 @@ import wx from 'weixin-js-sdk';
* @returns {string} 处理后的图片地址
*/
function normalize_image_url(src) {
if (!src) return '';
if (src.includes('cdn.ipadbiz.cn')) {
const compress = 'imageMogr2/thumbnail/200x/strip/quality/70';
if (src.includes('?')) {
return src.includes(compress) ? src : `${src}&${compress}`;
}
return `${src}?${compress}`;
if (!src) return ''
if (src.includes('cdn.ipadbiz.cn')) {
const compress = 'imageMogr2/thumbnail/200x/strip/quality/70'
if (src.includes('?')) {
return src.includes(compress) ? src : `${src}&${compress}`
}
return src;
return `${src}?${compress}`
}
return src
}
/**
......@@ -41,30 +41,30 @@ function normalize_image_url(src) {
* @param {string} params.imgUrl 分享图标地址
* @returns {void}
*/
export const sharePage = ({ title = '美乐爱觉', desc = '', imgUrl = '' }) => {
const shareData = {
title, // 分享标题
desc, // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,需与公众号 JS 安全域名一致
imgUrl: normalize_image_url(imgUrl), // 分享图标,按规则追加压缩参数
success: function () {
// 设置成功回调
}
}
export const sharePage = ({ title = '生命力教育联盟', desc = '', imgUrl = '' }) => {
const shareData = {
title, // 分享标题
desc, // 分享描述
link: location.origin + location.pathname + location.hash, // 分享链接,需与公众号 JS 安全域名一致
imgUrl: normalize_image_url(imgUrl), // 分享图标,按规则追加压缩参数
success() {
// 设置成功回调
},
}
if (wx && typeof wx.ready === 'function') {
wx.ready(() => {
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData);
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData);
// 分享到腾讯微博
if (typeof wx.onMenuShareWeibo === 'function') {
wx.onMenuShareWeibo(shareData);
}
});
} else {
// 微信 JSSDK 未初始化或未就绪,分享配置可能不会生效
console.warn('微信 JSSDK 未就绪:分享配置可能未生效');
}
if (wx && typeof wx.ready === 'function') {
wx.ready(() => {
// 分享好友(微信好友或qq好友)
wx.updateAppMessageShareData(shareData)
// 分享到朋友圈或qq空间
wx.updateTimelineShareData(shareData)
// 分享到腾讯微博
if (typeof wx.onMenuShareWeibo === 'function') {
wx.onMenuShareWeibo(shareData)
}
})
} else {
// 微信 JSSDK 未初始化或未就绪,分享配置可能不会生效
console.warn('微信 JSSDK 未就绪:分享配置可能未生效')
}
}
......
一、总则
1、“美乐爱觉”软件及相关服务,系指杭州南风教育科技有限公司(以下简称“南方教育”)合法拥有并运营的、标注名称为“美乐爱觉”的客户端、网站(https://wxm.behalo.cc)、WAP、小程序、公众号等及相关网站向您提供的产品与服务(以下统称“美乐爱觉平台”)。《“ 美乐爱觉平台”用户服务协议》(以下称“本协议”)是您与公司就您下载、安装、注册、登录、使用(以下统称“使用”)美乐爱觉互联网平台终端,并获得美乐爱觉平台提供的相关服务所订立的协议。
2、在此特别提醒您(以下或称“用户”)在注册成为美乐爱觉平台用户之前,应当认真阅读本《用户服务协议》(以下简称“协议”),确保您充分理解本协议中各条款,并选择接受或不接受本协议。其中,对于免除或限制责任条款等内容将以加粗形式提醒您注意,您应重点阅读。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为即视为您已充分阅读、理解并同意完全接受本协议的全部内容,用户应当受本协议的约束。如您未满18周岁,请您应在法定监护人的陪同下仔细阅读本协议并充分理解本协议,并征得法定监护人的同意后方可注册成为美乐爱觉平台用户。
3、本协议约定美乐爱觉平台与用户之间关于“美乐爱觉平台”服务(以下简称“服务”)的权利义务。“用户”是指注册、登录、使用本服务的个人。本协议可由美乐爱觉平台随时更新,更新后的协议条款一旦公布即代替原来的协议条款,并将以弹窗或其他形式提醒用户。用户可在本APP中查阅最新版协议条款。在修改协议条款后,如您不接受修改后的条款,您也可以选择停止使用,美乐爱觉平台亦有权因此终止您的注册进程及服务使用。您继续使用美乐爱觉平台提供的服务,则视为您已充分理解最新协议,并同意作为本协议的一方当事人接受本协议以及其他与“美乐爱觉平台”软件及相关服务相关的协议和规则(包括但不限于《“美乐爱觉平台”隐私政策》)的约束。
1、“生命力教育联盟”软件及相关服务,系指杭州南风教育科技有限公司(以下简称“南方教育”)合法拥有并运营的、标注名称为“生命力教育联盟”的客户端、网站(https://wxm.behalo.cc)、WAP、小程序、公众号等及相关网站向您提供的产品与服务(以下统称“生命力教育联盟平台”)。《“ 生命力教育联盟平台”用户服务协议》(以下称“本协议”)是您与公司就您下载、安装、注册、登录、使用(以下统称“使用”)生命力教育联盟互联网平台终端,并获得生命力教育联盟平台提供的相关服务所订立的协议。
2、在此特别提醒您(以下或称“用户”)在注册成为生命力教育联盟平台用户之前,应当认真阅读本《用户服务协议》(以下简称“协议”),确保您充分理解本协议中各条款,并选择接受或不接受本协议。其中,对于免除或限制责任条款等内容将以加粗形式提醒您注意,您应重点阅读。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为即视为您已充分阅读、理解并同意完全接受本协议的全部内容,用户应当受本协议的约束。如您未满18周岁,请您应在法定监护人的陪同下仔细阅读本协议并充分理解本协议,并征得法定监护人的同意后方可注册成为生命力教育联盟平台用户。
3、本协议约定生命力教育联盟平台与用户之间关于“生命力教育联盟平台”服务(以下简称“服务”)的权利义务。“用户”是指注册、登录、使用本服务的个人。本协议可由生命力教育联盟平台随时更新,更新后的协议条款一旦公布即代替原来的协议条款,并将以弹窗或其他形式提醒用户。用户可在本APP中查阅最新版协议条款。在修改协议条款后,如您不接受修改后的条款,您也可以选择停止使用,生命力教育联盟平台亦有权因此终止您的注册进程及服务使用。您继续使用生命力教育联盟平台提供的服务,则视为您已充分理解最新协议,并同意作为本协议的一方当事人接受本协议以及其他与“生命力教育联盟平台”软件及相关服务相关的协议和规则(包括但不限于《“生命力教育联盟平台”隐私政策》)的约束。
二、账号注册
1、"美乐爱觉平台"公众号、小程序、APP、网站等基础功能使用无需注册登录,部分涉及用户数据的功能在使用前需要注册一个“美乐爱觉平台”账号,经“美乐爱觉平台”注册系统完成注册程序并通过身份认证的用户即成为正式用户,可以获得本协议规定用户应当享有的一切权限;未经身份认证用户不享有任何使用权限。
2、“美乐爱觉平台”账号可使用手机号码或第三方平台账户(微信、QQ、Appleid)绑定注册,请用户使用尚未与“美乐爱觉平台”账号绑定的账户,以及未被美乐爱觉平台根据本协议封禁的账户注册“美乐爱觉平台”账号。美乐爱觉平台可以根据用户需求或产品需要对账号注册和绑定的方式进行变更,而无须事先通知用户。
3、如果注册申请者有被美乐爱觉平台封禁的先例或涉嫌虚假注册及滥用他人名义注册,及其他不能得到许可的理由,美乐爱觉平台将拒绝其注册申请。
4、在用户注册及使用本服务时,美乐爱觉平台需要搜集能识别用户身份的个人信息以便美乐爱觉平台可以在必要时联系用户,或为用户提供更好的使用体验。您同意美乐爱觉平台为向您提供服务而收集您的个人信息,美乐爱觉平台搜集的信息包括但不限于用户手机号码、第三方平台账户、昵称等用户同意授权的信息。美乐爱觉平台对这些信息的使用将受限于用户个人隐私信息保护的约束。
1、"生命力教育联盟平台"公众号、小程序、APP、网站等基础功能使用无需注册登录,部分涉及用户数据的功能在使用前需要注册一个“生命力教育联盟平台”账号,经“生命力教育联盟平台”注册系统完成注册程序并通过身份认证的用户即成为正式用户,可以获得本协议规定用户应当享有的一切权限;未经身份认证用户不享有任何使用权限。
2、“生命力教育联盟平台”账号可使用手机号码或第三方平台账户(微信、QQ、Appleid)绑定注册,请用户使用尚未与“生命力教育联盟平台”账号绑定的账户,以及未被生命力教育联盟平台根据本协议封禁的账户注册“生命力教育联盟平台”账号。生命力教育联盟平台可以根据用户需求或产品需要对账号注册和绑定的方式进行变更,而无须事先通知用户。
3、如果注册申请者有被生命力教育联盟平台封禁的先例或涉嫌虚假注册及滥用他人名义注册,及其他不能得到许可的理由,生命力教育联盟平台将拒绝其注册申请。
4、在用户注册及使用本服务时,生命力教育联盟平台需要搜集能识别用户身份的个人信息以便生命力教育联盟平台可以在必要时联系用户,或为用户提供更好的使用体验。您同意生命力教育联盟平台为向您提供服务而收集您的个人信息,生命力教育联盟平台搜集的信息包括但不限于用户手机号码、第三方平台账户、昵称等用户同意授权的信息。生命力教育联盟平台对这些信息的使用将受限于用户个人隐私信息保护的约束。
三、用户使用规则
1、您理解并承诺,您所设置的帐号不得违反国家法律法规及“美乐爱觉平台”的相关规则,您的帐号名称、头像和简介等注册信息及其他个人信息中不得出现违法和不良信息,未经他人许可不得用他人名义(包括但不限于冒用他人姓名、名称、字号、头像等或采取其他足以让人引起混淆的方式)开设帐号,不得恶意注册“美乐爱觉平台”帐号(包括但不限于频繁注册、批量注册帐号等行为)。您在帐号注册及使用过程中需遵守相关法律法规,不得实施任何侵害国家利益、损害其他公民合法权益,有害社会道德风尚的行为。公司有权对您提交的注册信息进行审核。
2、您有责任维护个人帐号、密码的安全性与保密性,并对您以注册帐号名义所从事的活动承担全部法律责任,包括但不限于您在“美乐爱觉平台”软件及相关服务上进行的任何数据修改、言论发表、款项支付等操作行为可能引起的一切法律责任。您应高度重视对帐号与密码的保密,在任何情况下不向他人透露帐号及密码。若发现他人未经许可使用您的帐号或发生其他任何安全漏洞问题时,您应当立即通知公司。
3、用户同意:美乐爱觉在提供服务的过程中以各种方式投放推广信息(包括但不限于:电子邮件、网站联络方式、在美乐爱觉平台的任何位置上投放),用户同意接受美乐爱觉以上述方式向用户发送推广信息。
1、您理解并承诺,您所设置的帐号不得违反国家法律法规及“生命力教育联盟平台”的相关规则,您的帐号名称、头像和简介等注册信息及其他个人信息中不得出现违法和不良信息,未经他人许可不得用他人名义(包括但不限于冒用他人姓名、名称、字号、头像等或采取其他足以让人引起混淆的方式)开设帐号,不得恶意注册“生命力教育联盟平台”帐号(包括但不限于频繁注册、批量注册帐号等行为)。您在帐号注册及使用过程中需遵守相关法律法规,不得实施任何侵害国家利益、损害其他公民合法权益,有害社会道德风尚的行为。公司有权对您提交的注册信息进行审核。
2、您有责任维护个人帐号、密码的安全性与保密性,并对您以注册帐号名义所从事的活动承担全部法律责任,包括但不限于您在“生命力教育联盟平台”软件及相关服务上进行的任何数据修改、言论发表、款项支付等操作行为可能引起的一切法律责任。您应高度重视对帐号与密码的保密,在任何情况下不向他人透露帐号及密码。若发现他人未经许可使用您的帐号或发生其他任何安全漏洞问题时,您应当立即通知公司。
3、用户同意:生命力教育联盟在提供服务的过程中以各种方式投放推广信息(包括但不限于:电子邮件、网站联络方式、在生命力教育联盟平台的任何位置上投放),用户同意接受生命力教育联盟以上述方式向用户发送推广信息。
四、账户安全
1、用户一旦注册成功,成为美乐爱觉平台的用户,将有权利使用自己的用户名及密码随时登陆美乐爱觉平台。
1、用户一旦注册成功,成为生命力教育联盟平台的用户,将有权利使用自己的用户名及密码随时登陆生命力教育联盟平台。
2、用户对用户名和密码的安全负责,同时对以其账户进行的所有活动和事件负全部责任。
3、用户不得以任何形式擅自转让或授权他人使用自己的美乐爱觉平台账户。
4、如果用户泄漏了账户密码,或用户发现任何人未经授权使用您的账号和密码的情况,应当立即修改密码并与美乐爱觉平台客服人员取得联系,并授权美乐爱觉平台暂停提供服务及展开调查,根据调查结果作出相应处理。用户理解美乐爱觉平台根据用户请求采取行动需要合理时间,美乐爱觉平台对采取行动前已经产生的后果和损失不承担任何责任。
5、用户若发现任何非法使用用户账号或存在安全漏洞的情况,请立即通知美乐爱觉平台工作人员。
6、因黑客行为或用户的保管疏忽导致账号非法使用,“美乐爱觉平台”不承担任何责任。
3、用户不得以任何形式擅自转让或授权他人使用自己的生命力教育联盟平台账户。
4、如果用户泄漏了账户密码,或用户发现任何人未经授权使用您的账号和密码的情况,应当立即修改密码并与生命力教育联盟平台客服人员取得联系,并授权生命力教育联盟平台暂停提供服务及展开调查,根据调查结果作出相应处理。用户理解生命力教育联盟平台根据用户请求采取行动需要合理时间,生命力教育联盟平台对采取行动前已经产生的后果和损失不承担任何责任。
5、用户若发现任何非法使用用户账号或存在安全漏洞的情况,请立即通知生命力教育联盟平台工作人员。
6、因黑客行为或用户的保管疏忽导致账号非法使用,“生命力教育联盟平台”不承担任何责任。
五、用户声明与保证
1、用户承诺其为具有完全民事行为能力的民事主体,且具有履行本协议约定义务的能力。
2、用户保证其为履行本协议而向美乐爱觉平台提供的全部资料均真实、有效。用户有义务在注册时提供自己的真实身份信息,并保证诸如手机号码、姓名等必要身份信息的有效性及安全性,保证美乐爱觉平台工作人员可以通过上述联系方式与用户取得联系。同时,用户也有义务在相关身份信息实际变更时及时更新有关注册资料。
3、用户在使用美乐爱觉平台账号或本服务的过程中所制作、上载、复制、发布、传播的任何内容,不得违反国家相关法律制度,包括但不限于:
2、用户保证其为履行本协议而向生命力教育联盟平台提供的全部资料均真实、有效。用户有义务在注册时提供自己的真实身份信息,并保证诸如手机号码、姓名等必要身份信息的有效性及安全性,保证生命力教育联盟平台工作人员可以通过上述联系方式与用户取得联系。同时,用户也有义务在相关身份信息实际变更时及时更新有关注册资料。
3、用户在使用生命力教育联盟平台账号或本服务的过程中所制作、上载、复制、发布、传播的任何内容,不得违反国家相关法律制度,包括但不限于:
(1)反对宪法所确定的基本原则的;
(2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;
(3)损害国家荣誉和利益的;
......@@ -42,68 +42,68 @@
(9)煽动非法集会、结社、游行、示威、聚众扰乱社会秩序的;
(10)以非法民间组织名义活动的;
(11)含有法律、行政法规禁止的其他内容的。
4、用户不得利用美乐爱觉平台账号或本服务制作、上载、复制、发布、传播任何干扰美乐爱觉平台正常运营,以及侵犯其他用户或第三方合法权益的内容,包括但不限于:
4、用户不得利用生命力教育联盟平台账号或本服务制作、上载、复制、发布、传播任何干扰生命力教育联盟平台正常运营,以及侵犯其他用户或第三方合法权益的内容,包括但不限于:
(1)含有任何性或性暗示的;
(2)含有辱骂、恐吓、威胁内容的;
(3)含有骚扰、垃圾广告、恶意信息、诱骗信息的;
(4)涉及他人隐私、个人信息或资料的;
(5)侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的;
(6)含有其他干扰本服务正常运营和侵犯其他用户或第三方合法权益内容的信息。
5、用户承若对其发表或者上传美乐爱觉平台的所有信息均享有完整的知识产权,或者已经得到相关权利人的合法授权,如用户违反本条规定造成美乐爱觉平台被第三人索赔的,用户应全额赔偿美乐爱觉平台一切损失费用(包括但不限于各种赔偿费、律师代理费、诉讼费及为此支出的其他合理费用)。
6、当第三人认为用户发表或上传美乐爱觉平台的信息侵犯其权利,并根据相关法律法规向美乐爱觉平台发送权利通知书时,用户同意美乐爱觉平台可以自行判断决定删除涉嫌侵权信息,除非用户提交书面证据材料排除侵权的可能性,美乐爱觉平台不会自动回复上述删除信息。
5、用户承若对其发表或者上传生命力教育联盟平台的所有信息均享有完整的知识产权,或者已经得到相关权利人的合法授权,如用户违反本条规定造成生命力教育联盟平台被第三人索赔的,用户应全额赔偿生命力教育联盟平台一切损失费用(包括但不限于各种赔偿费、律师代理费、诉讼费及为此支出的其他合理费用)。
6、当第三人认为用户发表或上传生命力教育联盟平台的信息侵犯其权利,并根据相关法律法规向生命力教育联盟平台发送权利通知书时,用户同意生命力教育联盟平台可以自行判断决定删除涉嫌侵权信息,除非用户提交书面证据材料排除侵权的可能性,生命力教育联盟平台不会自动回复上述删除信息。
六、服务内容
1、美乐爱觉平台具体服务内容由美乐爱觉平台根据实际情况提供,包括但不限于:知识文章、视频课程等。具体以美乐爱觉平台实际提供的功能或服务为准。
2、用户认可,美乐爱觉平台发给用户的所有通知、公告及其他消息都可通过用户所提供的联系方式向用户送达或通知。
1、生命力教育联盟平台具体服务内容由生命力教育联盟平台根据实际情况提供,包括但不限于:知识文章、视频课程等。具体以生命力教育联盟平台实际提供的功能或服务为准。
2、用户认可,生命力教育联盟平台发给用户的所有通知、公告及其他消息都可通过用户所提供的联系方式向用户送达或通知。
七、付费课程相关
美乐爱觉平台提供中华传统国学相关付费视频课程,视频课程属于虚拟计算机软件类商品。根据《消费者权益保护法》第二十五条规定,计算机软件等数字化商品不支持7天无理由退款,故当你购买付费服务成功后,不支持无条件退款。
生命力教育联盟平台提供中华传统国学相关付费视频课程,视频课程属于虚拟计算机软件类商品。根据《消费者权益保护法》第二十五条规定,计算机软件等数字化商品不支持7天无理由退款,故当你购买付费服务成功后,不支持无条件退款。
视频课程知识产权归本公司所有,用户付费购买的视频,用户仅享有播放权,不具备视频下载、视频剪辑等、视频传播等其他权利,若未经“杭州南风教育科技有限公司”许可,进行视频下载、视频剪辑等行为,本公司将依法追究其违法行为。
八、服务的暂停或终止
1、在下列情况下,美乐爱觉平台有权视具体情况自主决定暂停或终止向用户提供服务:
(1)在用户违反本服务协议相关规定时,美乐爱觉平台有权视具体情况自主决定暂停或终止向该用户提供服务。
(2)如美乐爱觉平台通过用户提供的信息与用户联系时,发现用户在注册时绑定的联系方式已不存在或无法与用户取得联系的,美乐爱觉平台将以系统通知的方式告知用户更改,如用户在三个工作日内仍未能提供新的联系方式,美乐爱觉平台有权终止向该用户提供服务。
1、在下列情况下,生命力教育联盟平台有权视具体情况自主决定暂停或终止向用户提供服务:
(1)在用户违反本服务协议相关规定时,生命力教育联盟平台有权视具体情况自主决定暂停或终止向该用户提供服务。
(2)如生命力教育联盟平台通过用户提供的信息与用户联系时,发现用户在注册时绑定的联系方式已不存在或无法与用户取得联系的,生命力教育联盟平台将以系统通知的方式告知用户更改,如用户在三个工作日内仍未能提供新的联系方式,生命力教育联盟平台有权终止向该用户提供服务。
(3)本服务条款终止或更新时,用户明示不愿接受新的服务条款的。
(4)用户主动注销账户或用户向美乐爱觉平台申请注销其账户,经美乐爱觉平台审核同意的。
(5)其他美乐爱觉平台认为需终止服务的情况。
(4)用户主动注销账户或用户向生命力教育联盟平台申请注销其账户,经生命力教育联盟平台审核同意的。
(5)其他生命力教育联盟平台认为需终止服务的情况。
2、用户理解并同意:
(1)服务终止后,美乐爱觉平台没有义务为用户保留原账号中或与之相关的任何信息,或转发任何未曾阅读或发送的信息给用户或第三方。
(2)用户在使用本服务期间存在违法行为或违反本协议行为的,美乐爱觉平台仍可依据本协议想用户主张权利或依法依规向行政、司法等机关进行披露。
(1)服务终止后,生命力教育联盟平台没有义务为用户保留原账号中或与之相关的任何信息,或转发任何未曾阅读或发送的信息给用户或第三方。
(2)用户在使用本服务期间存在违法行为或违反本协议行为的,生命力教育联盟平台仍可依据本协议想用户主张权利或依法依规向行政、司法等机关进行披露。
(3)用户在使用本服务期间与其他第三方(如有)之间发生的关系,不因本服务或本协议的终止而终止,第三方仍有权向用户主张权利,用户应继续向第三方履行相关义务。
3、美乐爱觉平台保有删除APP内各类不符合法律政策或者不实信息内容而无须通知用户的权利。
4、若用户未遵守本协议规定的或其他服务条件的行为,美乐爱觉平台有权作出独立判断并采取暂停或关闭用户账号等措施,对于因此而造成用户无法正常使用账号及相关服务、无法正常获取用户账号内权益的等,美乐爱觉平台不承担任何责任。用户须对自己在网上的言论和行为承担法律责任。对于涉嫌违反法律法规、涉嫌违法犯罪的行为、美乐爱觉平台将保存有关记录,并有权依法向有关主管部门报告、配合有关主管部门调查、向公安机关报案等。
3、生命力教育联盟平台保有删除APP内各类不符合法律政策或者不实信息内容而无须通知用户的权利。
4、若用户未遵守本协议规定的或其他服务条件的行为,生命力教育联盟平台有权作出独立判断并采取暂停或关闭用户账号等措施,对于因此而造成用户无法正常使用账号及相关服务、无法正常获取用户账号内权益的等,生命力教育联盟平台不承担任何责任。用户须对自己在网上的言论和行为承担法律责任。对于涉嫌违反法律法规、涉嫌违法犯罪的行为、生命力教育联盟平台将保存有关记录,并有权依法向有关主管部门报告、配合有关主管部门调查、向公安机关报案等。
九、服务的变更、中断
1、用户理解并同意,美乐爱觉平台提供的服务是按照现有技术和条件所能达到的现状提供的。用户亦明确知道使用服务存在一定信息风险,美乐爱觉平台将尽力维护用户使用服务的合法权益,但不担保服务一定能满足用户要求,也不担保服务的及时性、安全性、真实性、稳定性、正确性,对用户使用服务中出现的信息(包括但不限于用户发布的信息删除或存储失败),美乐爱觉平台为此不承担任何责任。
2、用户理解并同意,美乐爱觉平台为了整体服务运营安全需要或定期或不定期地检测或者更新需要,有权视具体情况决定服务变更、中断、中止或终止服务,但美乐爱觉平台将尽可能事先进行通告。
3、用户理解并同意,鉴于网络服务的特殊性,服务可能会受到多种因素(包括但不限于境内外基础运营商的网络故障、技术缺陷、覆盖范围限制、不可抗力、计算机病毒、黑客攻击或其他非美乐爱觉平台技术能力范围内的事因等)的影响或干扰,美乐爱觉平台不能随时或始终预见和防范上述因素造成的服务中断、用户存储信息丢失、未保存、出现乱码、错误接收、无法接收、延迟接收等,美乐爱觉平台无需为此对任何用户或任何第三方承担责任。若发生上述因此,美乐爱觉平台将尽可能及时通过公告、系统通知等其他合理方式通知受到影响的用户。
1、用户理解并同意,生命力教育联盟平台提供的服务是按照现有技术和条件所能达到的现状提供的。用户亦明确知道使用服务存在一定信息风险,生命力教育联盟平台将尽力维护用户使用服务的合法权益,但不担保服务一定能满足用户要求,也不担保服务的及时性、安全性、真实性、稳定性、正确性,对用户使用服务中出现的信息(包括但不限于用户发布的信息删除或存储失败),生命力教育联盟平台为此不承担任何责任。
2、用户理解并同意,生命力教育联盟平台为了整体服务运营安全需要或定期或不定期地检测或者更新需要,有权视具体情况决定服务变更、中断、中止或终止服务,但生命力教育联盟平台将尽可能事先进行通告。
3、用户理解并同意,鉴于网络服务的特殊性,服务可能会受到多种因素(包括但不限于境内外基础运营商的网络故障、技术缺陷、覆盖范围限制、不可抗力、计算机病毒、黑客攻击或其他非生命力教育联盟平台技术能力范围内的事因等)的影响或干扰,生命力教育联盟平台不能随时或始终预见和防范上述因素造成的服务中断、用户存储信息丢失、未保存、出现乱码、错误接收、无法接收、延迟接收等,生命力教育联盟平台无需为此对任何用户或任何第三方承担责任。若发生上述因此,生命力教育联盟平台将尽可能及时通过公告、系统通知等其他合理方式通知受到影响的用户。
十、知识产权条款
1、北京美乐爱觉有限公司对本服务中所有内容,包括但不限于设计、画面安排、软件架构、图片、文章、视频等均由北京美乐爱觉有限公司依法拥有其知识产权(包括但不限于商标权、专利权、著作权、商业秘密等)。
2、非经北京美乐爱觉有限公司书面同意,任何人不得擅自使用、修改、复制、公开传播、改变、散布、发行或公开发表美乐爱觉平台程序或内容。
1、北京生命力教育联盟有限公司对本服务中所有内容,包括但不限于设计、画面安排、软件架构、图片、文章、视频等均由北京生命力教育联盟有限公司依法拥有其知识产权(包括但不限于商标权、专利权、著作权、商业秘密等)。
2、非经北京生命力教育联盟有限公司书面同意,任何人不得擅自使用、修改、复制、公开传播、改变、散布、发行或公开发表生命力教育联盟平台程序或内容。
3、尊重知识产权是用户应尽的义务,如有违反,应承担相应赔偿责任。
十一、服务条款修改
1、美乐爱觉平台有权随时修改本服务条款的任何内容,一旦本服务条款的任何内容发生变动,美乐爱觉平台将会通过适当方式(包括但不限于弹窗)向用户提示修改内容。
2、如果不同意美乐爱觉平台对本服务条款所做的修改,用户有权停止使用本服务。
3、如果用户继续使用网络服务,则视为用户接受美乐爱觉平台对本服务条款所做的修改。
1、生命力教育联盟平台有权随时修改本服务条款的任何内容,一旦本服务条款的任何内容发生变动,生命力教育联盟平台将会通过适当方式(包括但不限于弹窗)向用户提示修改内容。
2、如果不同意生命力教育联盟平台对本服务条款所做的修改,用户有权停止使用本服务。
3、如果用户继续使用网络服务,则视为用户接受生命力教育联盟平台对本服务条款所做的修改。
十二、隐私保护
请阅读《隐私保护政策》。
十三、其他
1、若美乐爱觉平台已经明示其服务提供方式发生变更并提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。
2、用户同意保障和维护美乐爱觉平台及其他用户的利益,由于用户在使用美乐爱觉平台有违法、不真实、不正当、侵犯第三方合法权益的行为,或用户违反本协议项下的任何条款而给美乐爱觉平台及任何其他第三方造成损失,用户同意承担由此造成的损害赔偿责任。
3、本协议的效力、解释及纠纷的解决,适用于中华人民共和国法律。若用户和美乐爱觉平台之间发生任何纠纷或争议,首先应友好协商解决,协商不成的,任何一方均有权向杭州南风教育科技有限公司住所地人民法院提起诉讼。
1、若生命力教育联盟平台已经明示其服务提供方式发生变更并提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。
2、用户同意保障和维护生命力教育联盟平台及其他用户的利益,由于用户在使用生命力教育联盟平台有违法、不真实、不正当、侵犯第三方合法权益的行为,或用户违反本协议项下的任何条款而给生命力教育联盟平台及任何其他第三方造成损失,用户同意承担由此造成的损害赔偿责任。
3、本协议的效力、解释及纠纷的解决,适用于中华人民共和国法律。若用户和生命力教育联盟平台之间发生任何纠纷或争议,首先应友好协商解决,协商不成的,任何一方均有权向杭州南风教育科技有限公司住所地人民法院提起诉讼。
4、本协议的任何条款被司法部门认定为无效或不具可执行性,或违反所须适用的法律的,则该条款将被视为无效,其余条款仍然有效,对双方具有约束力。
5、本协议最终解释权归杭州南风教育科技有限公司所有。
十四、免责声明
1、用户明确同意其使用美乐爱觉平台服务,以及对美乐爱觉平台服务加以依赖所存在的全部责任和风险将完全有其自己承担,因其使用美乐爱觉平台服务过程的行为,以及因此而产生的一切后果也由其自己承担,美乐爱觉平台对用户不承担任何责任。
2、美乐爱觉平台不担保服务一定能满足用户要求,也不担保服务不会中断,对服务的及时性、安全性、准确性也都不作担保。
3、用户因使用本服务而产生的任何间接的、附带的、特殊的、结果性的或惩戒性的损害,美乐爱觉平台概不负责,均由用户自行承担。
\ No newline at end of file
1、用户明确同意其使用生命力教育联盟平台服务,以及对生命力教育联盟平台服务加以依赖所存在的全部责任和风险将完全有其自己承担,因其使用生命力教育联盟平台服务过程的行为,以及因此而产生的一切后果也由其自己承担,生命力教育联盟平台对用户不承担任何责任。
2、生命力教育联盟平台不担保服务一定能满足用户要求,也不担保服务不会中断,对服务的及时性、安全性、准确性也都不作担保。
3、用户因使用本服务而产生的任何间接的、附带的、特殊的、结果性的或惩戒性的损害,生命力教育联盟平台概不负责,均由用户自行承担。
......
......@@ -23,7 +23,7 @@ export const routes = [
path: '/',
name: 'HomePage',
component: () => import('../views/HomePage.vue'),
meta: { title: '美乐爱觉' },
meta: { title: '生命力教育联盟' },
},
{
path: '/courses',
......@@ -158,7 +158,7 @@ export const routes = [
path: '/recall/choose',
name: 'ChoosePage',
component: () => import('../views/recall/ChoosePage.vue'),
meta: { title: '美乐爱觉AI星球' },
meta: { title: '生命力教育联盟AI星球' },
},
{
path: '/checkout',
......
......@@ -3,7 +3,7 @@
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2026-01-20 16:30:23
* @FilePath: /mlaj/src/views/HomePage.vue
* @Description: 美乐爱觉教育首页组件
* @Description: 生命力教育联盟教育首页组件
*
* 主要功能模块:
* 1. 用户欢迎区:显示用户信息和每日打卡
......@@ -29,18 +29,23 @@
-->
<template>
<AppLayout title="美乐爱觉教育">
<AppLayout title="生命力教育联盟教育">
<div class="bg-gradient-to-b from-white via-green-50/10 to-blue-50/10">
<!-- Header Section with Welcome & Weather -->
<div v-if="currentUser" class="px-4 pt-3 pb-4">
<FrostedGlass class="p-4 rounded-xl mb-4">
<div class="flex justify-between items-center mb-3">
<div v-if="currentUser" class="px-4 pb-4 pt-3">
<FrostedGlass class="mb-4 rounded-xl p-4">
<div class="mb-3 flex items-center justify-between">
<div class="flex items-center">
<div class="w-10 h-10 rounded-full overflow-hidden mr-3">
<div class="mr-3 h-10 w-10 overflow-hidden rounded-full">
<img
:src="buildCdnImageUrl(currentUser?.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg')"
class="w-full h-full object-cover"
@error="handleImageError" />
:src="
buildCdnImageUrl(
currentUser?.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'
)
"
class="h-full w-full object-cover"
@error="handleImageError"
/>
</div>
<div>
<h2 class="text-xl font-bold">欢迎回来,{{ currentUser.name || '登录用户' }}!</h2>
......@@ -57,25 +62,25 @@
</div>
<!-- User Stats -->
<div class="flex justify-between text-center py-2">
<div class=" border-gray-200 flex-1">
<div class="text-lg font-bold flex items-baseline justify-center">
<div class="flex justify-between py-2 text-center">
<div class="flex-1 border-gray-200">
<div class="flex items-baseline justify-center text-lg font-bold">
<span>{{ currentUser?.total_days || 0 }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500">累计打卡</div>
</div>
<div class="border-gray-200 flex-1">
<div class="text-lg font-bold flex items-baseline justify-center">
<div class="flex-1 border-gray-200">
<div class="flex items-baseline justify-center text-lg font-bold">
<span>{{ currentUser?.consecutive_days || 0 }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500">连续打卡</div>
</div>
<div class="border-gray-200 flex-1">
<div class="text-lg font-bold flex items-baseline justify-center">
<div class="flex-1 border-gray-200">
<div class="flex items-baseline justify-center text-lg font-bold">
<span>{{ currentUser?.longest_consecutive_days || 0 }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500">最长连续</div>
</div>
......@@ -83,13 +88,19 @@
</FrostedGlass>
<!-- Daily Check-in -->
<FrostedGlass class="p-4 rounded-xl">
<div class="flex justify-between items-center mb-3">
<FrostedGlass class="rounded-xl p-4">
<div class="mb-3 flex items-center justify-between">
<h3 class="font-medium">今日打卡</h3>
<router-link to="/profile" class="text-green-600 text-sm">打卡记录</router-link>
<router-link to="/profile" class="text-sm text-green-600">打卡记录</router-link>
</div>
<template v-if="checkInTypes.length">
<CheckInList :items="checkInTypes" dense scroll :plain="true" @submit-success="handleHomeCheckInSuccess" />
<CheckInList
:items="checkInTypes"
dense
scroll
:plain="true"
@submit-success="handleHomeCheckInSuccess"
/>
</template>
<template v-else>
<div class="text-center">
......@@ -165,7 +176,7 @@
</div> -->
<!-- Content Based on Active Tab -->
<div class="px-4 mt-5" ref="contentRef">
<div class="mt-5 px-4" ref="contentRef">
<!-- Recommended Content -->
<div v-if="activeTab === '推荐'">
<RecommendationsSection />
......@@ -176,73 +187,85 @@
<!-- Live Content -->
<div v-if="activeTab === '直播'">
<section>
<div class="flex justify-between items-center mb-3">
<div class="mb-3 flex items-center justify-between">
<h3 class="font-medium">正在直播</h3>
<div class="text-xs text-red-500 flex items-center">
<div class="w-2 h-2 bg-red-500 rounded-full mr-1 animate-pulse"></div>
<div class="flex items-center text-xs text-red-500">
<div class="mr-1 h-2 w-2 animate-pulse rounded-full bg-red-500"></div>
2个直播中
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-7">
<LiveStreamCard
v-for="stream in liveStreams"
:key="stream.id"
:stream="stream"
/>
<div class="mb-7 grid grid-cols-2 gap-4">
<LiveStreamCard v-for="stream in liveStreams" :key="stream.id" :stream="stream" />
</div>
<div class="mb-5">
<div class="flex justify-between items-center mb-3">
<div class="mb-3 flex items-center justify-between">
<h3 class="font-medium">直播日历</h3>
<router-link to="/live-calendar" class="text-xs text-blue-500">
查看日历
</router-link>
</div>
<FrostedGlass class="p-3 rounded-xl">
<FrostedGlass class="rounded-xl p-3">
<div class="flex space-x-2 overflow-x-auto py-1">
<div
v-for="(day, i) in ['今天', '明天', '周三', '周四', '周五', '周六', '周日']"
:key="day"
:class="[
'flex-shrink-0 w-10 h-14 flex flex-col items-center justify-center rounded-lg',
i === 0 ? 'bg-green-500 text-white' : 'bg-white/50'
'flex h-14 w-10 flex-shrink-0 flex-col items-center justify-center rounded-lg',
i === 0 ? 'bg-green-500 text-white' : 'bg-white/50',
]"
>
<div class="text-xs">{{ day }}</div>
<div class="font-bold mt-1">{{ new Date().getDate() + i }}</div>
<div class="mt-1 font-bold">{{ new Date().getDate() + i }}</div>
</div>
</div>
</FrostedGlass>
</div>
<div>
<h3 class="font-medium mb-3">直播预告</h3>
<h3 class="mb-3 font-medium">直播预告</h3>
<div class="space-y-3">
<FrostedGlass
v-for="(item, index) in [
{ title: '亲子阅读会第1期', time: '今天 19:30-20:30', image: 'https://cdn.ipadbiz.cn/mlaj/images/live-1.jpg' },
{ title: '儿童心理健康讲座', time: '明天 20:00-21:00', image: 'https://cdn.ipadbiz.cn/mlaj/images/live-2.jpg' },
{ title: '家庭教育经验分享', time: '周三 19:00-20:00', image: 'https://cdn.ipadbiz.cn/mlaj/images/live-3.jpg' }
{
title: '亲子阅读会第1期',
time: '今天 19:30-20:30',
image: 'https://cdn.ipadbiz.cn/mlaj/images/live-1.jpg',
},
{
title: '儿童心理健康讲座',
time: '明天 20:00-21:00',
image: 'https://cdn.ipadbiz.cn/mlaj/images/live-2.jpg',
},
{
title: '家庭教育经验分享',
time: '周三 19:00-20:00',
image: 'https://cdn.ipadbiz.cn/mlaj/images/live-3.jpg',
},
]"
:key="index"
class="p-3 rounded-xl"
class="rounded-xl p-3"
>
<div class="flex justify-between items-center">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="w-12 h-12 bg-green-100 rounded-lg overflow-hidden mr-3 flex-shrink-0">
<div
class="mr-3 h-12 w-12 flex-shrink-0 overflow-hidden rounded-lg bg-green-100"
>
<img
:src="buildCdnImageUrl(item.image)"
:alt="item.title"
class="w-full h-full object-cover"
class="h-full w-full object-cover"
@error="handleImageError"
/>
</div>
<div>
<h4 class="font-medium text-sm">{{ item.title }}</h4>
<p class="text-xs text-gray-500 mt-1">{{ item.time }}</p>
<h4 class="text-sm font-medium">{{ item.title }}</h4>
<p class="mt-1 text-xs text-gray-500">{{ item.time }}</p>
</div>
</div>
<button class="bg-white text-green-600 border border-green-600 px-3 py-1 rounded-full text-xs flex-shrink-0">
<button
class="flex-shrink-0 rounded-full border border-green-600 bg-white px-3 py-1 text-xs text-green-600"
>
预约
</button>
</div>
......@@ -256,17 +279,19 @@
<div v-if="activeTab === '精选'">
<section>
<div class="mb-5">
<h3 class="font-medium mb-3">精选内容</h3>
<FrostedGlass class="p-4 rounded-xl">
<h3 class="mb-3 font-medium">精选内容</h3>
<FrostedGlass class="rounded-xl p-4">
<div class="flex flex-col">
<div class="inline-block px-2 py-1 bg-blue-100 text-blue-600 text-xs rounded-full mb-2 w-fit">
<div
class="mb-2 inline-block w-fit rounded-full bg-blue-100 px-2 py-1 text-xs text-blue-600"
>
独家专栏
</div>
<h4 class="font-medium text-lg mb-2">《如何培养孩子的阅读习惯》</h4>
<p class="text-gray-600 text-sm mb-4 line-clamp-2">
<h4 class="mb-2 text-lg font-medium">《如何培养孩子的阅读习惯》</h4>
<p class="mb-4 line-clamp-2 text-sm text-gray-600">
阅读习惯的培养是一个长期过程,本文将分享如何从日常生活点滴培养孩子的阅读兴趣和习惯...
</p>
<router-link to="/articles/1" class="text-green-600 text-sm font-medium">
<router-link to="/articles/1" class="text-sm font-medium text-green-600">
查看完整文章
</router-link>
</div>
......@@ -274,34 +299,68 @@
</div>
<div>
<h3 class="font-medium mb-3">推荐视频</h3>
<h3 class="mb-3 font-medium">推荐视频</h3>
<div class="space-y-4">
<div
v-for="(item, index) in [
{ title: '亲子沟通的艺术', views: '1.2万', duration: '08:25', image: 'https://cdn.ipadbiz.cn/mlaj/images/video-1.jpg', video_url: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4' },
{ title: '如何做好家庭教育', views: '8千', duration: '12:40', image: 'https://cdn.ipadbiz.cn/mlaj/images/video-2.jpg', video_url: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4' },
{ title: '孩子营养餐制作指南', views: '5千', duration: '15:18', image: 'https://cdn.ipadbiz.cn/mlaj/images/video-3.jpg', video_url: 'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4' }
{
title: '亲子沟通的艺术',
views: '1.2万',
duration: '08:25',
image: 'https://cdn.ipadbiz.cn/mlaj/images/video-1.jpg',
video_url:
'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
},
{
title: '如何做好家庭教育',
views: '8千',
duration: '12:40',
image: 'https://cdn.ipadbiz.cn/mlaj/images/video-2.jpg',
video_url:
'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
},
{
title: '孩子营养餐制作指南',
views: '5千',
duration: '15:18',
image: 'https://cdn.ipadbiz.cn/mlaj/images/video-3.jpg',
video_url:
'https://sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4',
},
]"
:key="index"
class="relative rounded-xl overflow-hidden shadow-md h-48"
class="relative h-48 overflow-hidden rounded-xl shadow-md"
>
<template v-if="activeVideoIndex !== index">
<img
:src="buildCdnImageUrl(item.image, 400)"
:alt="item.title"
class="w-full h-full object-cover"
class="h-full w-full object-cover"
@error="handleImageError"
/>
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/70 flex flex-col justify-end p-4">
<h4 class="text-white font-medium mb-1">{{ item.title }}</h4>
<div class="flex justify-between items-center">
<p class="text-white/80 text-xs">{{ item.views }}次播放 · {{ item.duration }}</p>
<div
class="absolute inset-0 flex flex-col justify-end bg-gradient-to-b from-transparent to-black/70 p-4"
>
<h4 class="mb-1 font-medium text-white">{{ item.title }}</h4>
<div class="flex items-center justify-between">
<p class="text-xs text-white/80">
{{ item.views }}次播放 · {{ item.duration }}
</p>
<button
class="bg-white/20 backdrop-blur-sm p-2 rounded-full"
class="rounded-full bg-white/20 p-2 backdrop-blur-sm"
@click="playVideo(index, item.video_url)"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-white"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>
......@@ -349,7 +408,7 @@ import { useHomeVideoPlayer } from '@/composables/useHomeVideoPlayer'
import { useImageLoader } from '@/composables/useImageLoader'
// 导入接口
import { getTaskListAPI } from "@/api/checkin";
import { getTaskListAPI } from '@/api/checkin'
import { normalizeCheckinTaskItems, buildCdnImageUrl } from '@/utils/tools'
// 图片加载错误处理
......@@ -369,16 +428,20 @@ const { currentUser } = useAuth()
const activeTab = ref('推荐') // 当前激活的内容标签页
// 签到列表
const checkInTypes = ref([]);
const checkInTypes = ref([])
onMounted(() => {
watch(() => currentUser.value, async (newVal) => {
if (!newVal) return
const task = await getTaskListAPI()
if (task && task.code) {
checkInTypes.value = normalizeCheckinTaskItems(task.data)
}
}, { immediate: true })
watch(
() => currentUser.value,
async newVal => {
if (!newVal) return
const task = await getTaskListAPI()
if (task && task.code) {
checkInTypes.value = normalizeCheckinTaskItems(task.data)
}
},
{ immediate: true }
)
})
// 工具函数:格式化今天的日期为中文格式
......@@ -394,13 +457,13 @@ const formatToday = () => {
* @returns {Promise<void>}
*/
const handleHomeCheckInSuccess = async () => {
// 轻提示
showToast('打卡成功')
// 统一刷新:重新获取签到任务列表并更新置灰状态
const task = await getTaskListAPI()
if (task?.code) {
checkInTypes.value = normalizeCheckinTaskItems(task.data)
}
// 轻提示
showToast('打卡成功')
// 统一刷新:重新获取签到任务列表并更新置灰状态
const task = await getTaskListAPI()
if (task?.code) {
checkInTypes.value = normalizeCheckinTaskItems(task.data)
}
}
const contentRef = ref(null) // 内容区域的ref引用
......@@ -414,7 +477,7 @@ watch(activeTab, () => {
const marginTop = parseInt(window.getComputedStyle(contentRef.value).marginTop)
window.scrollTo({
top: contentRef.value.offsetTop - navHeight - marginTop,
behavior:'smooth'
behavior: 'smooth',
})
}
})
......
<template>
<div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
<div
class="flex min-h-screen flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 px-4 py-12 sm:px-6 lg:px-8"
>
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1>
<h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1>
<h2 class="text-center text-xl font-medium text-gray-600">重置密码</h2>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<FrostedGlass class="py-8 px-6 rounded-lg">
<div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
<FrostedGlass class="rounded-lg px-6 py-8">
<div
v-if="error"
class="mb-4 rounded-md border border-red-400 bg-red-100 px-4 py-3 text-red-700"
>
{{ error }}
</div>
......@@ -25,7 +30,7 @@
maxlength="11"
@input="formData.phone = formData.phone.replace(/\D/g, '')"
@blur="validatePhone"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -40,13 +45,13 @@
type="text"
required
maxlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
<button
type="button"
:disabled="countdown > 0 || !isPhoneValid"
@click="sendVerificationCode"
class="mt-1 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
class="mt-1 whitespace-nowrap rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
</button>
......@@ -63,7 +68,7 @@
type="password"
required
minlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -77,7 +82,7 @@
type="password"
required
minlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -85,15 +90,15 @@
<button
type="submit"
:disabled="loading"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
:class="{ 'opacity-70 cursor-not-allowed': loading }"
class="flex w-full justify-center rounded-md border border-transparent bg-gradient-to-r from-green-500 to-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"
:class="{ 'cursor-not-allowed opacity-70': loading }"
>
{{ loading ? '提交中...' : '重置密码' }}
</button>
</div>
</form>
<div class="text-center mt-6">
<div class="mt-6 text-center">
<p class="text-sm text-gray-600">
记起密码了?
<router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
......@@ -110,9 +115,9 @@
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import FrostedGlass from '@/components/effects/FrostedGlass.vue'
import { smsAPI } from '@/api/common';
import { resetPasswordAPI } from '@/api/users';
import { showToast } from 'vant';
import { smsAPI } from '@/api/common'
import { resetPasswordAPI } from '@/api/users'
import { showToast } from 'vant'
const router = useRouter()
const error = ref('')
......@@ -124,7 +129,7 @@ const formData = reactive({
phone: '',
verificationCode: '',
password: '',
confirmPassword: ''
confirmPassword: '',
})
const startCountdown = () => {
......@@ -187,7 +192,7 @@ const handleSubmit = async () => {
const { code } = await resetPasswordAPI({
mobile: formData.phone,
sms_code: formData.verificationCode,
password: formData.password
password: formData.password,
})
if (code === 1) {
......@@ -195,7 +200,6 @@ const handleSubmit = async () => {
// 重置成功后跳转到登录页
router.push('/login')
}
} catch (err) {
console.error('Reset password error:', err)
error.value = '重置密码失败,请稍后重试'
......
......@@ -8,7 +8,7 @@
size="10rem"
style="margin-bottom: 0.5rem"
/>
<h1 class="mb-2 text-center text-3xl font-bold text-gray-800">美乐爱觉教育</h1>
<h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1>
<!-- <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2> -->
</div>
......@@ -168,7 +168,7 @@
class="cursor-pointer font-medium text-green-600 hover:text-green-500"
@click="userAgreementRef.openAgreement()"
>
美乐爱觉宇宙用户协议》
生命力教育联盟宇宙用户协议》
</span>
<UserAgreement ref="userAgreementRef" />
</p>
......
<template>
<div class="min-h-screen flex flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 py-12 px-4 sm:px-6 lg:px-8">
<div
class="flex min-h-screen flex-col bg-gradient-to-br from-green-50 via-teal-50 to-blue-50 px-4 py-12 sm:px-6 lg:px-8"
>
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1>
<h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1>
<h2 class="text-center text-xl font-medium text-gray-600">创建账号</h2>
</div>
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<FrostedGlass class="py-8 px-6 rounded-lg">
<div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md">
<FrostedGlass class="rounded-lg px-6 py-8">
<div
v-if="error"
class="mb-4 rounded-md border border-red-400 bg-red-100 px-4 py-3 text-red-700"
>
{{ error }}
</div>
......@@ -21,7 +26,7 @@
v-model="formData.name"
type="text"
required
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -39,7 +44,7 @@
maxlength="11"
@input="formData.phone = formData.phone.replace(/\D/g, '')"
@blur="validatePhone"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -54,13 +59,13 @@
type="text"
required
maxlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
<button
type="button"
:disabled="countdown > 0 || !isPhoneValid"
@click="sendVerificationCode"
class="mt-1 px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
class="mt-1 whitespace-nowrap rounded-md border border-transparent bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
{{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }}
</button>
......@@ -78,7 +83,7 @@
autocomplete="new-password"
required
minlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -93,7 +98,7 @@
autocomplete="new-password"
required
minlength="6"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-green-500 focus:border-green-500"
class="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500"
/>
</div>
......@@ -105,15 +110,25 @@
checked-color="#4caf50"
class="scale-90"
icon-size="18px"
><span class="text-sm">我已阅读并同意 <a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openTerms">用户协议</a> 和 <a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openPrivacy">隐私政策</a></span></van-checkbox>
><span class="text-sm"
>我已阅读并同意
<a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openTerms"
>用户协议</a
>
<a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openPrivacy"
>隐私政策</a
></span
></van-checkbox
>
</div>
<div>
<button
type="submit"
:disabled="loading"
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gradient-to-r from-green-500 to-green-600 hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500"
:class="{ 'opacity-70 cursor-not-allowed': loading }"
class="flex w-full justify-center rounded-md border border-transparent bg-gradient-to-r from-green-500 to-green-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:from-green-600 hover:to-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2"
:class="{ 'cursor-not-allowed opacity-70': loading }"
>
{{ loading ? '注册中...' : '立即注册' }}
</button>
......@@ -154,7 +169,7 @@
</div>
</div> -->
<div class="text-center mt-6">
<div class="mt-6 text-center">
<p class="text-sm text-gray-600">
已有账号?
<router-link to="/login" class="font-medium text-green-600 hover:text-green-500">
......@@ -165,16 +180,8 @@
</FrostedGlass>
</div>
<TermsPopup
v-model:show="showTerms"
:type="popupType"
:title="popupTitle"
/>
<TermsPopup
v-model:show="showPrivacy"
:type="popupType"
:title="popupTitle"
/>
<TermsPopup v-model:show="showTerms" :type="popupType" :title="popupTitle" />
<TermsPopup v-model:show="showPrivacy" :type="popupType" :title="popupTitle" />
</div>
</template>
......@@ -184,13 +191,13 @@ import { useRoute, useRouter } from 'vue-router'
import FrostedGlass from '@/components/effects/FrostedGlass.vue'
import TermsPopup from '@/components/common/TermsPopup.vue'
import { useAuth } from '@/contexts/auth'
import { useTitle } from '@vueuse/core';
import { smsAPI } from '@/api/common';
import { registerAPI } from '@/api/users';
import { showToast } from 'vant';
import { useTitle } from '@vueuse/core'
import { smsAPI } from '@/api/common'
import { registerAPI } from '@/api/users'
import { showToast } from 'vant'
const $route = useRoute();
useTitle($route.meta.title);
const $route = useRoute()
useTitle($route.meta.title)
const router = useRouter()
const { login } = useAuth()
......@@ -203,7 +210,7 @@ const formData = reactive({
verificationCode: '',
password: '',
confirmPassword: '',
agreeTerms: false
agreeTerms: false,
})
const startCountdown = () => {
......@@ -293,7 +300,7 @@ const handleSubmit = async () => {
const success = login({
name: formData.name,
mobile: formData.phone,
avatar: ''
avatar: '',
})
if (success) {
router.push('/')
......
<template>
<AppLayout :right-content="rightContent">
<div
class="bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30 min-h-screen"
>
<div class="min-h-screen bg-gradient-to-br from-green-50 via-green-100/30 to-blue-50/30">
<!-- User Profile Header with Enhanced Design -->
<div class="pt-6 pb-8 relative">
<div
class="absolute inset-0 bg-gradient-to-r from-green-500 to-blue-500 opacity-15"
></div>
<div class="relative pb-8 pt-6">
<div class="absolute inset-0 bg-gradient-to-r from-green-500 to-blue-500 opacity-15"></div>
<div class="relative z-10 flex flex-col items-center">
<div
class="w-24 h-24 rounded-full overflow-hidden border-4 border-white shadow-lg mb-4"
>
<div class="mb-4 h-24 w-24 overflow-hidden rounded-full border-4 border-white shadow-lg">
<img
:src="profile.avatar || 'https://cdn.ipadbiz.cn/mlaj/images/icon_1.jpeg'"
:alt="profile.name"
class="w-full h-full object-cover"
class="h-full w-full object-cover"
@error="handleImageError"
/>
</div>
<h2 class="text-2xl font-bold mb-1">{{ profile.name || '匿名用户' }}</h2>
<h2 class="mb-1 text-2xl font-bold">{{ profile.name || '匿名用户' }}</h2>
<!-- <div class="flex items-center text-sm text-gray-600">
<span>会员等级: 普通会员</span>
<span class="mx-2">|</span>
......@@ -29,40 +23,46 @@
</div>
<!-- Check-in Statistics -->
<div class="px-4 mb-5 pt-4">
<FrostedGlass class="p-4 rounded-xl">
<div class="flex justify-between items-center mb-4">
<h3 class="font-semibold text-base">打卡统计</h3>
<div class="mb-5 px-4 pt-4">
<FrostedGlass class="rounded-xl p-4">
<div class="mb-4 flex items-center justify-between">
<h3 class="text-base font-semibold">打卡统计</h3>
<!-- <span class="text-xs text-blue-500">查看更多</span> -->
</div>
<div class="grid items-center gap-2" style="grid-template-columns: 1fr 1fr 1fr auto;">
<div class="flex flex-col items-center min-w-0">
<div class="font-bold text-gray-800 text-xl text-center w-full truncate px-0.5 flex items-baseline justify-center">
<div class="grid items-center gap-2" style="grid-template-columns: 1fr 1fr 1fr auto">
<div class="flex min-w-0 flex-col items-center">
<div
class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-gray-800"
>
<span>{{ formatCheckInCount(checkIns?.total_days) }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500 mt-1 truncate w-full text-center">累计打卡</div>
<div class="mt-1 w-full truncate text-center text-xs text-gray-500">累计打卡</div>
</div>
<div class="flex flex-col items-center min-w-0">
<div class="font-bold text-green-600 text-xl text-center w-full truncate px-0.5 flex items-baseline justify-center">
<div class="flex min-w-0 flex-col items-center">
<div
class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-green-600"
>
<span>{{ formatCheckInCount(checkIns?.consecutive_days) }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500 mt-1 truncate w-full text-center">连续打卡</div>
<div class="mt-1 w-full truncate text-center text-xs text-gray-500">连续打卡</div>
</div>
<div class="flex flex-col items-center min-w-0">
<div class="font-bold text-blue-600 text-xl text-center w-full truncate px-0.5 flex items-baseline justify-center">
<div class="flex min-w-0 flex-col items-center">
<div
class="flex w-full items-baseline justify-center truncate px-0.5 text-center text-xl font-bold text-blue-600"
>
<span>{{ formatCheckInCount(checkIns?.longest_consecutive_days) }}</span>
<span class="text-xs ml-1 font-normal">天</span>
<span class="ml-1 text-xs font-normal">天</span>
</div>
<div class="text-xs text-gray-500 mt-1 truncate w-full text-center">最长连续</div>
<div class="mt-1 w-full truncate text-center text-xs text-gray-500">最长连续</div>
</div>
<div>
<div @click="handleCheckin" class="cursor-pointer">
<button
class="bg-gradient-to-r from-green-500 to-green-600 text-white py-2 px-3 rounded-full text-sm shadow-sm whitespace-nowrap"
class="whitespace-nowrap rounded-full bg-gradient-to-r from-green-500 to-green-600 px-3 py-2 text-sm text-white shadow-sm"
>
立即打卡
</button>
......@@ -74,7 +74,7 @@
<!-- User Menu Options -->
<div class="px-4 pb-16">
<FrostedGlass class="rounded-xl overflow-hidden mb-5">
<FrostedGlass class="mb-5 overflow-hidden rounded-xl">
<MenuItem
v-for="item in menuItems1"
:key="item.path"
......@@ -83,7 +83,7 @@
/>
</FrostedGlass>
<FrostedGlass class="rounded-xl overflow-hidden mb-5">
<FrostedGlass class="mb-5 overflow-hidden rounded-xl">
<MenuItem
v-for="item in menuItems2"
:key="item.path"
......@@ -92,7 +92,7 @@
/>
</FrostedGlass>
<FrostedGlass v-if="isTeacher" class="rounded-xl overflow-hidden mb-5">
<FrostedGlass v-if="isTeacher" class="mb-5 overflow-hidden rounded-xl">
<MenuItem
v-for="item in menuItems4"
:key="item.path"
......@@ -101,7 +101,7 @@
/>
</FrostedGlass>
<FrostedGlass class="rounded-xl overflow-hidden mb-5">
<FrostedGlass class="mb-5 overflow-hidden rounded-xl">
<MenuItem
v-for="item in menuItems3"
:key="item.path"
......@@ -111,7 +111,7 @@
</FrostedGlass>
<!-- Version Info -->
<div class="text-center text-xs text-gray-400 mb-4">美乐爱觉教育</div>
<div class="mb-4 text-center text-xs text-gray-400">生命力教育联盟教育</div>
<!-- Logout Button -->
<!-- <button
......@@ -132,22 +132,22 @@
</template>
<script setup>
import { ref, h } from "vue";
import { useRoute, useRouter } from "vue-router";
import AppLayout from "@/components/layout/AppLayout.vue";
import FrostedGlass from "@/components/effects/FrostedGlass.vue";
import MenuItem from "@/components/common/MenuItem.vue";
import { useAuth } from "@/contexts/auth";
import CheckInDialog from "@/components/checkin/CheckInDialog.vue";
import { getUserInfoAPI } from "@/api/users";
import { showToast } from "vant";
import { useTitle } from "@vueuse/core";
import { useImageLoader } from "@/composables/useImageLoader";
import { useUserInfo } from "@/composables/useUserInfo";
import { ref, h } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AppLayout from '@/components/layout/AppLayout.vue'
import FrostedGlass from '@/components/effects/FrostedGlass.vue'
import MenuItem from '@/components/common/MenuItem.vue'
import { useAuth } from '@/contexts/auth'
import CheckInDialog from '@/components/checkin/CheckInDialog.vue'
import { getUserInfoAPI } from '@/api/users'
import { showToast } from 'vant'
import { useTitle } from '@vueuse/core'
import { useImageLoader } from '@/composables/useImageLoader'
import { useUserInfo } from '@/composables/useUserInfo'
const router = useRouter();
const $route = useRoute();
useTitle($route.meta.title);
const router = useRouter()
const $route = useRoute()
useTitle($route.meta.title)
// 图片加载错误处理
const { handleImageError } = useImageLoader()
......@@ -155,146 +155,145 @@ const { handleImageError } = useImageLoader()
// 用户信息获取
const { userInfo, refreshUserInfo } = useUserInfo()
const profile = ref({});
const checkIns = ref({});
const isTeacher = ref(false);
const profile = ref({})
const checkIns = ref({})
const isTeacher = ref(false)
const formatCheckInCount = (count) => {
const num = Number(count) || 0;
return num > 99 ? '99+' : num;
};
const formatCheckInCount = count => {
const num = Number(count) || 0
return num > 99 ? '99+' : num
}
onMounted(async () => {
const userData = await refreshUserInfo();
const userData = await refreshUserInfo()
if (userData) {
profile.value = userData;
profile.value = userData
checkIns.value = {
total_days: userData.total_days || 0,
consecutive_days: userData.consecutive_days || 0,
longest_consecutive_days: userData.longest_consecutive_days || 0
};
isTeacher.value = userData.is_teacher;
longest_consecutive_days: userData.longest_consecutive_days || 0,
}
isTeacher.value = userData.is_teacher
// 处理消息中心的未读消息数量
menuItems2.value.forEach(item => {
if (item.path === '/profile/messages') {
item.badge = +(userData?.unread_msg_count || 0);
item.badge = +(userData?.unread_msg_count || 0)
}
});
})
}
})
const showCheckInDialog = ref(false);
const showCheckInDialog = ref(false)
// 处理打卡成功
const handleCheckInSuccess = () => {
checkIns.value.total_days++;
checkIns.value.consecutive_days++;
checkIns.value.total_days++
checkIns.value.consecutive_days++
checkIns.value.longest_consecutive_days = Math.max(
checkIns.value.longest_consecutive_days,
checkIns.value.consecutive_days
);
};
)
}
const checkinData = ref([]);
const handleCheckInData = (data) => {
checkinData.value = data;
const checkinData = ref([])
const handleCheckInData = data => {
checkinData.value = data
}
// Handle logout
const { logout } = useAuth();
const { logout } = useAuth()
const handleLogout = () => {
logout();
logout()
// window.location.href = import.meta.env.VITE_BASE || '/';
// 返回首页
router.replace({ path: '/' });
};
router.replace({ path: '/' })
}
// Handle menu item click
const handleMenuClick = (path) => {
const handleMenuClick = path => {
if (path === '/profile/community') {
// window.location.href = 'https://community.mlaj.com';
showToast('功能暂未开放')
} else if(path === '/profile/activities') {
// showToast('功能暂未开放')
window.location.href = 'https://wxm.behalo.cc/pages/tabBar/mine/application?type=2';
} else if(path === '/profile/userinfo') {
// showToast('功能暂未开放')
window.location.href = 'https://wxm.behalo.cc/pages/student/student?token=&user_id=';
} else if (path === '/profile/activities') {
// showToast('功能暂未开放')
window.location.href = 'https://wxm.behalo.cc/pages/tabBar/mine/application?type=2'
} else if (path === '/profile/userinfo') {
// showToast('功能暂未开放')
window.location.href = 'https://wxm.behalo.cc/pages/student/student?token=&user_id='
} else {
router.push(path);
router.push(path)
}
};
}
// Handle check-in type click
const handleCheckInClick = (path) => {
router.push(path);
};
const handleCheckInClick = path => {
router.push(path)
}
// Right content component
const rightContent = h("div", { class: "flex items-center" }, [
h("button", { class: "p-2" }, [
const rightContent = h('div', { class: 'flex items-center' }, [
h('button', { class: 'p-2' }, [
h(
"svg",
'svg',
{
xmlns: "http://www.w3.org/2000/svg",
class: "h-6 w-6 text-gray-700",
fill: "none",
viewBox: "0 0 24 24",
stroke: "currentColor",
xmlns: 'http://www.w3.org/2000/svg',
class: 'h-6 w-6 text-gray-700',
fill: 'none',
viewBox: '0 0 24 24',
stroke: 'currentColor',
},
[
h("path", {
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-width": "2",
d:
"M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9",
h('path', {
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
'stroke-width': '2',
d: 'M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9',
}),
]
),
]),
]);
])
// Menu items
const menuItems1 = [
{
icon: "clock",
title: "学习记录",
path: "/profile/learning-records",
badge: "",
icon: 'clock',
title: '学习记录',
path: '/profile/learning-records',
badge: '',
},
{
icon: "wallet",
title: "活动报名",
path: "/profile/activities",
icon: 'wallet',
title: '活动报名',
path: '/profile/activities',
},
{
icon: "user",
title: "人员信息",
path: "/profile/userinfo",
icon: 'user',
title: '人员信息',
path: '/profile/userinfo',
},
{
icon: "book",
title: "我的课程",
path: "/profile/courses",
icon: 'book',
title: '我的课程',
path: '/profile/courses',
},
{
icon: "document",
title: "课程订单",
path: "/profile/orders",
icon: 'document',
title: '课程订单',
path: '/profile/orders',
},
];
]
const menuItems2 = ref([
{
icon: "wallet",
title: "我的积分",
path: "/profile/points",
icon: 'wallet',
title: '我的积分',
path: '/profile/points',
},
{
icon: "heart",
title: "我的收藏",
path: "/profile/favorites",
icon: 'heart',
title: '我的收藏',
path: '/profile/favorites',
},
// {
// icon: "chat",
......@@ -302,47 +301,47 @@ const menuItems2 = ref([
// path: "/profile/community",
// },
{
icon: "email",
title: "我的消息",
path: "/profile/messages",
badge: "",
icon: 'email',
title: '我的消息',
path: '/profile/messages',
badge: '',
},
]);
])
const menuItems3 = ref([
{
icon: "question",
title: "帮助中心",
path: "/profile/help",
icon: 'question',
title: '帮助中心',
path: '/profile/help',
},
{
icon: "settings",
title: "设置",
path: "/profile/settings",
icon: 'settings',
title: '设置',
path: '/profile/settings',
},
]);
])
const menuItems4 = ref([
{
icon: "document",
title: "打卡管理",
path: "/teacher/checkin",
icon: 'document',
title: '打卡管理',
path: '/teacher/checkin',
},
{
icon: "user",
title: "我的班级",
path: "/teacher/myClass",
icon: 'user',
title: '我的班级',
path: '/teacher/myClass',
},
{
icon: "book",
title: "作业管理",
path: "/teacher/tasks",
icon: 'book',
title: '作业管理',
path: '/teacher/tasks',
},
]);
])
const handleCheckin = () => {
if (checkinData.value.length) {
showCheckInDialog.value = true;
showCheckInDialog.value = true
} else {
showToast('暂无打卡任务')
}
......
<template>
<div class="recall-login w-full min-h-screen relative overflow-hidden flex flex-col items-center">
<div class="recall-login relative flex min-h-screen w-full flex-col items-center overflow-hidden">
<!-- <VideoBackground /> -->
<StarryBackground />
<!-- 标题区域 -->
<div class="mt-10 flex flex-col items-center z-10 w-full px-8">
<img :src="titleImg" class="w-full max-w-[300px] mb-4 object-contain" alt="title" />
<div class="z-10 mt-10 flex w-full flex-col items-center px-8">
<img :src="titleImg" class="mb-4 w-full max-w-[300px] object-contain" alt="title" />
<div class="text-white text-center space-y-1 tracking-wider text-shadow-md">
<div class="text-shadow-md space-y-1 text-center tracking-wider text-white">
<p class="text-sm">请验证身份</p>
<p class="text-sm">开启专属您的「时光机」</p>
<p class="text-sm">见证每一段成长故事</p>
......@@ -15,66 +15,105 @@
</div>
<!-- Login Form Module -->
<div class="w-full px-6 mt-6 z-10">
<div class="z-10 mt-6 w-full px-6">
<!-- Using existing FrostedGlass component for semi-transparent effect -->
<FrostedGlass class="p-6 !rounded-2xl !border-white/20 !shadow-none" :bg-opacity="10" blur-level="md">
<h2 class="text-white text-center text-xl font-bold mb-8 tracking-widest drop-shadow-md">手机号验证</h2>
<FrostedGlass
class="!rounded-2xl !border-white/20 p-6 !shadow-none"
:bg-opacity="10"
blur-level="md"
>
<h2 class="mb-8 text-center text-xl font-bold tracking-widest text-white drop-shadow-md">
手机号验证
</h2>
<div class="space-y-5">
<!-- Phone Input -->
<van-field v-model="phone" placeholder="请输入手机号" class="custom-input rounded-lg !bg-white/10 !text-white !border-[1px] !border-solid !border-[rgba(255,255,255,0.57)]"
:border="false" type="tel" maxlength="11" />
<van-field
v-model="phone"
placeholder="请输入手机号"
class="custom-input rounded-lg !border-[1px] !border-solid !border-[rgba(255,255,255,0.57)] !bg-white/10 !text-white"
:border="false"
type="tel"
maxlength="11"
/>
<!-- Verification Code Input -->
<div class="flex gap-3">
<van-field v-model="code" placeholder="请输入验证码"
class="custom-input rounded-lg !bg-white/10 !text-white flex-1 !border-[1px] !border-solid !border-[rgba(255,255,255,0.57)]" :border="false" type="digit"
maxlength="6" />
<van-button plain
class="verify-btn !bg-transparent !text-white !border-[#FFDD01] !rounded-lg !px-4 !h-[44px] !font-normal"
@click="handleSendCode" :disabled="counting">
<van-field
v-model="code"
placeholder="请输入验证码"
class="custom-input flex-1 rounded-lg !border-[1px] !border-solid !border-[rgba(255,255,255,0.57)] !bg-white/10 !text-white"
:border="false"
type="digit"
maxlength="6"
/>
<van-button
plain
class="verify-btn !h-[44px] !rounded-lg !border-[#FFDD01] !bg-transparent !px-4 !font-normal !text-white"
@click="handleSendCode"
:disabled="counting"
>
{{ counting ? `${count}s` : '获取验证码' }}
</van-button>
</div>
<!-- Next Step Button -->
<van-button block
class="submit-btn !mt-8 !rounded-lg !bg-transparent !border-[#FFDD01] !text-[#FFDD01] !font-bold !text-lg !h-[48px]"
@click="handleLogin">
<van-button
block
class="submit-btn !mt-8 !h-[48px] !rounded-lg !border-[#FFDD01] !bg-transparent !text-lg !font-bold !text-[#FFDD01]"
@click="handleLogin"
>
下一步
</van-button>
</div>
<!-- Footer Agreement Text -->
<div class="mt-6 text-center text-xs text-white/90 leading-relaxed">
<div class="mt-6 text-center text-xs leading-relaxed text-white/90">
点击“下一步”,即表示您同意我们的
<br />
<span class="text-[#FFDD01] cursor-pointer hover:opacity-80 transition-opacity"
@click="showAgreement = true">《美乐爱觉宇宙用户协议》</span>
<span
class="cursor-pointer text-[#FFDD01] transition-opacity hover:opacity-80"
@click="showAgreement = true"
>《生命力教育联盟宇宙用户协议》</span
>
</div>
</FrostedGlass>
</div>
<!-- Agreement Popup -->
<van-popup v-model:show="showAgreement" round position="bottom" :style="{ height: '70%' }" closeable
class="agreement-popup">
<div class="flex flex-col h-full bg-white">
<div class="p-4 text-center border-b border-gray-100 bg-white sticky top-0 z-10">
<h3 class="font-bold text-lg text-gray-800">美乐爱觉宇宙用户协议</h3>
<van-popup
v-model:show="showAgreement"
round
position="bottom"
:style="{ height: '70%' }"
closeable
class="agreement-popup"
>
<div class="flex h-full flex-col bg-white">
<div class="sticky top-0 z-10 border-b border-gray-100 bg-white p-4 text-center">
<h3 class="text-lg font-bold text-gray-800">生命力教育联盟宇宙用户协议</h3>
</div>
<div class="flex-1 overflow-y-auto p-5 scroll-smooth">
<div class="text-sm text-gray-600 leading-relaxed text-justify space-y-4">
<div class="flex-1 overflow-y-auto scroll-smooth p-5">
<div class="space-y-4 text-justify text-sm leading-relaxed text-gray-600">
<div v-for="(section, index) in agreementSections" :key="index">
<h4 v-if="section.title" class="font-bold text-gray-800 mb-2 mt-4 text-base">{{ section.title }}</h4>
<h4 v-if="section.title" class="mb-2 mt-4 text-base font-bold text-gray-800">
{{ section.title }}
</h4>
<p v-for="(para, pIndex) in section.content" :key="pIndex" class="mb-2 indent-2">
{{ para }}
</p>
</div>
</div>
</div>
<div class="p-4 border-t border-gray-100 bg-white safe-area-bottom">
<van-button block round type="primary" color="linear-gradient(to right, #FFDD01, #E5C600)"
class="!text-white !font-bold !h-[44px]" @click="showAgreement = false">
<div class="safe-area-bottom border-t border-gray-100 bg-white p-4">
<van-button
block
round
type="primary"
color="linear-gradient(to right, #FFDD01, #E5C600)"
class="!h-[44px] !font-bold !text-white"
@click="showAgreement = false"
>
我已阅读并同意
</van-button>
</div>
......@@ -88,7 +127,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { showToast } from 'vant'
import { useTitle } from '@vueuse/core'
import { setAuthHeaders } from "@/utils/axios";
import { setAuthHeaders } from '@/utils/axios'
import VideoBackground from '@/components/media/VideoBackground.vue'
import { applyUserInfoAuth } from '@/utils/auth_user_info'
......@@ -173,26 +212,34 @@ const handleLogin = async () => {
}
try {
const res = await loginAPI({ mobile: phone.value, sms_code: code.value, entry: entry.value, referrer_user_id: referrer_user_id.value })
const res = await loginAPI({
mobile: phone.value,
sms_code: code.value,
entry: entry.value,
referrer_user_id: referrer_user_id.value,
})
if (res.code === 1) {
applyUserInfoAuth(res, { set_auth_headers: setAuthHeaders, storage: localStorage })
const userInfo = await userInfoAPI()
// 登录之后需要判断是否有完善个人信息
if (userInfo.code === 1) {
const info = userInfo.data.user || '';
if (!info.has_idcard) { // 如果【查询我的信息】没有填写过身份证(has_idcard 为 false),则进入完善个人信息页
const info = userInfo.data.user || ''
if (!info.has_idcard) {
// 如果【查询我的信息】没有填写过身份证(has_idcard 为 false),则进入完善个人信息页
$router.push('/recall/boot')
} else { // 如果【查询我的信息】填写过身份证(has_idcard 为 true)
if (!info.has_activity_registration) { // 如果【查询我的信息】中没有兑换过星球币(has_activity_registration 为 false),则进入二次查询历史活动页
} else {
// 如果【查询我的信息】填写过身份证(has_idcard 为 true)
if (!info.has_activity_registration) {
// 如果【查询我的信息】中没有兑换过星球币(has_activity_registration 为 false),则进入二次查询历史活动页
$router.push('/recall/id-query')
} else { // 如果【查询我的信息】中兑换过星球币(has_activity_registration 为 true),则进入时光机页面
} else {
// 如果【查询我的信息】中兑换过星球币(has_activity_registration 为 true),则进入时光机页面
$router.push('/recall/timeline')
}
}
}
}
}
catch (error) {
} catch (error) {
console.error('登录失败:', error)
showToast('登录失败,请稍后重试')
}
......@@ -203,45 +250,45 @@ const agreementSections = [
{
title: '一、总则',
content: [
'1、“美乐爱觉”软件及相关服务,系指杭州南风教育科技有限公司(以下简称“南方教育”)合法拥有并运营的、标注名称为“美乐爱觉”的客户端、网站(https://wxm.behalo.cc)、WAP、小程序、公众号等及相关网站向您提供的产品与服务(以下统称“美乐爱觉平台”)。《“ 美乐爱觉平台”用户服务协议》(以下称“本协议”)是您与公司就您下载、安装、注册、登录、使用(以下统称“使用”)美乐爱觉互联网平台终端,并获得美乐爱觉平台提供的相关服务所订立的协议。',
'2、在此特别提醒您(以下或称“用户”)在注册成为美乐爱觉平台用户之前,应当认真阅读本《用户服务协议》(以下简称“协议”),确保您充分理解本协议中各条款,并选择接受或不接受本协议。其中,对于免除或限制责任条款等内容将以加粗形式提醒您注意,您应重点阅读。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为即视为您已充分阅读、理解并同意完全接受本协议的全部内容,用户应当受本协议的约束。如您未满18周岁,请您应在法定监护人的陪同下仔细阅读本协议并充分理解本协议,并征得法定监护人的同意后方可注册成为美乐爱觉平台用户。',
'3、本协议约定美乐爱觉平台与用户之间关于“美乐爱觉平台”服务(以下简称“服务”)的权利义务。“用户”是指注册、登录、使用本服务的个人。本协议可由美乐爱觉平台随时更新,更新后的协议条款一旦公布即代替原来的协议条款,并将以弹窗或其他形式提醒用户。用户可在本APP中查阅最新版协议条款。在修改协议条款后,如您不接受修改后的条款,您也可以选择停止使用,美乐爱觉平台亦有权因此终止您的注册进程及服务使用。您继续使用美乐爱觉平台提供的服务,则视为您已充分理解最新协议,并同意作为本协议的一方当事人接受本协议以及其他与“美乐爱觉平台”软件及相关服务相关的协议和规则(包括但不限于《“美乐爱觉平台”隐私政策》)的约束。'
]
'1、“生命力教育联盟”软件及相关服务,系指杭州南风教育科技有限公司(以下简称“南方教育”)合法拥有并运营的、标注名称为“生命力教育联盟”的客户端、网站(https://wxm.behalo.cc)、WAP、小程序、公众号等及相关网站向您提供的产品与服务(以下统称“生命力教育联盟平台”)。《“ 生命力教育联盟平台”用户服务协议》(以下称“本协议”)是您与公司就您下载、安装、注册、登录、使用(以下统称“使用”)生命力教育联盟互联网平台终端,并获得生命力教育联盟平台提供的相关服务所订立的协议。',
'2、在此特别提醒您(以下或称“用户”)在注册成为生命力教育联盟平台用户之前,应当认真阅读本《用户服务协议》(以下简称“协议”),确保您充分理解本协议中各条款,并选择接受或不接受本协议。其中,对于免除或限制责任条款等内容将以加粗形式提醒您注意,您应重点阅读。除非您接受本协议所有条款,否则您无权注册、登录或使用本协议所涉服务。您的注册、登录、使用等行为即视为您已充分阅读、理解并同意完全接受本协议的全部内容,用户应当受本协议的约束。如您未满18周岁,请您应在法定监护人的陪同下仔细阅读本协议并充分理解本协议,并征得法定监护人的同意后方可注册成为生命力教育联盟平台用户。',
'3、本协议约定生命力教育联盟平台与用户之间关于“生命力教育联盟平台”服务(以下简称“服务”)的权利义务。“用户”是指注册、登录、使用本服务的个人。本协议可由生命力教育联盟平台随时更新,更新后的协议条款一旦公布即代替原来的协议条款,并将以弹窗或其他形式提醒用户。用户可在本APP中查阅最新版协议条款。在修改协议条款后,如您不接受修改后的条款,您也可以选择停止使用,生命力教育联盟平台亦有权因此终止您的注册进程及服务使用。您继续使用生命力教育联盟平台提供的服务,则视为您已充分理解最新协议,并同意作为本协议的一方当事人接受本协议以及其他与“生命力教育联盟平台”软件及相关服务相关的协议和规则(包括但不限于《“生命力教育联盟平台”隐私政策》)的约束。',
],
},
{
title: '二、账号注册',
content: [
'1、"美乐爱觉平台"公众号、小程序、APP、网站等基础功能使用无需注册登录,部分涉及用户数据的功能在使用前需要注册一个“美乐爱觉平台”账号,经“美乐爱觉平台”注册系统完成注册程序并通过身份认证的用户即成为正式用户,可以获得本协议规定用户应当享有的一切权限;未经身份认证用户不享有任何使用权限。',
'2、“美乐爱觉平台”账号可使用手机号码或第三方平台账户(微信、QQ、Appleid)绑定注册,请用户使用尚未与“美乐爱觉平台”账号绑定的账户,以及未被美乐爱觉平台根据本协议封禁的账户注册“美乐爱觉平台”账号。美乐爱觉平台可以根据用户需求或产品需要对账号注册和绑定的方式进行变更,而无须事先通知用户。',
'3、如果注册申请者有被美乐爱觉平台封禁的先例或涉嫌虚假注册及滥用他人名义注册,及其他不能得到许可的理由,美乐爱觉平台将拒绝其注册申请。',
'4、在用户注册及使用本服务时,美乐爱觉平台需要搜集能识别用户身份的个人信息以便美乐爱觉平台可以在必要时联系用户,或为用户提供更好的使用体验。您同意美乐爱觉平台为向您提供服务而收集您的个人信息,美乐爱觉平台搜集的信息包括但不限于用户手机号码、第三方平台账户、昵称等用户同意授权的信息。美乐爱觉平台对这些信息的使用将受限于用户个人隐私信息保护的约束。'
]
'1、"生命力教育联盟平台"公众号、小程序、APP、网站等基础功能使用无需注册登录,部分涉及用户数据的功能在使用前需要注册一个“生命力教育联盟平台”账号,经“生命力教育联盟平台”注册系统完成注册程序并通过身份认证的用户即成为正式用户,可以获得本协议规定用户应当享有的一切权限;未经身份认证用户不享有任何使用权限。',
'2、“生命力教育联盟平台”账号可使用手机号码或第三方平台账户(微信、QQ、Appleid)绑定注册,请用户使用尚未与“生命力教育联盟平台”账号绑定的账户,以及未被生命力教育联盟平台根据本协议封禁的账户注册“生命力教育联盟平台”账号。生命力教育联盟平台可以根据用户需求或产品需要对账号注册和绑定的方式进行变更,而无须事先通知用户。',
'3、如果注册申请者有被生命力教育联盟平台封禁的先例或涉嫌虚假注册及滥用他人名义注册,及其他不能得到许可的理由,生命力教育联盟平台将拒绝其注册申请。',
'4、在用户注册及使用本服务时,生命力教育联盟平台需要搜集能识别用户身份的个人信息以便生命力教育联盟平台可以在必要时联系用户,或为用户提供更好的使用体验。您同意生命力教育联盟平台为向您提供服务而收集您的个人信息,生命力教育联盟平台搜集的信息包括但不限于用户手机号码、第三方平台账户、昵称等用户同意授权的信息。生命力教育联盟平台对这些信息的使用将受限于用户个人隐私信息保护的约束。',
],
},
{
title: '三、用户使用规则',
content: [
'1、您理解并承诺,您所设置的帐号不得违反国家法律法规及“美乐爱觉平台”的相关规则,您的帐号名称、头像和简介等注册信息及其他个人信息中不得出现违法和不良信息,未经他人许可不得用他人名义(包括但不限于冒用他人姓名、名称、字号、头像等或采取其他足以让人引起混淆的方式)开设帐号,不得恶意注册“美乐爱觉平台”帐号(包括但不限于频繁注册、批量注册帐号等行为)。您在帐号注册及使用过程中需遵守相关法律法规,不得实施任何侵害国家利益、损害其他公民合法权益,有害社会道德风尚的行为。公司有权对您提交的注册信息进行审核。',
'2、您有责任维护个人帐号、密码的安全性与保密性,并对您以注册帐号名义所从事的活动承担全部法律责任,包括但不限于您在“美乐爱觉平台”软件及相关服务上进行的任何数据修改、言论发表、款项支付等操作行为可能引起的一切法律责任。您应高度重视对帐号与密码的保密,在任何情况下不向他人透露帐号及密码。若发现他人未经许可使用您的帐号或发生其他任何安全漏洞问题时,您应当立即通知公司。',
'3、用户同意:美乐爱觉在提供服务的过程中以各种方式投放推广信息(包括但不限于:电子邮件、网站联络方式、在美乐爱觉平台的任何位置上投放),用户同意接受美乐爱觉以上述方式向用户发送推广信息。'
]
'1、您理解并承诺,您所设置的帐号不得违反国家法律法规及“生命力教育联盟平台”的相关规则,您的帐号名称、头像和简介等注册信息及其他个人信息中不得出现违法和不良信息,未经他人许可不得用他人名义(包括但不限于冒用他人姓名、名称、字号、头像等或采取其他足以让人引起混淆的方式)开设帐号,不得恶意注册“生命力教育联盟平台”帐号(包括但不限于频繁注册、批量注册帐号等行为)。您在帐号注册及使用过程中需遵守相关法律法规,不得实施任何侵害国家利益、损害其他公民合法权益,有害社会道德风尚的行为。公司有权对您提交的注册信息进行审核。',
'2、您有责任维护个人帐号、密码的安全性与保密性,并对您以注册帐号名义所从事的活动承担全部法律责任,包括但不限于您在“生命力教育联盟平台”软件及相关服务上进行的任何数据修改、言论发表、款项支付等操作行为可能引起的一切法律责任。您应高度重视对帐号与密码的保密,在任何情况下不向他人透露帐号及密码。若发现他人未经许可使用您的帐号或发生其他任何安全漏洞问题时,您应当立即通知公司。',
'3、用户同意:生命力教育联盟在提供服务的过程中以各种方式投放推广信息(包括但不限于:电子邮件、网站联络方式、在生命力教育联盟平台的任何位置上投放),用户同意接受生命力教育联盟以上述方式向用户发送推广信息。',
],
},
{
title: '四、账户安全',
content: [
'1、用户一旦注册成功,成为美乐爱觉平台的用户,将有权利使用自己的用户名及密码随时登陆美乐爱觉平台。',
'1、用户一旦注册成功,成为生命力教育联盟平台的用户,将有权利使用自己的用户名及密码随时登陆生命力教育联盟平台。',
'2、用户对用户名和密码的安全负责,同时对以其账户进行的所有活动和事件负全部责任。',
'3、用户不得以任何形式擅自转让或授权他人使用自己的美乐爱觉平台账户。',
'4、如果用户泄漏了账户密码,或用户发现任何人未经授权使用您的账号和密码的情况,应当立即修改密码并与美乐爱觉平台客服人员取得联系,并授权美乐爱觉平台暂停提供服务及展开调查,根据调查结果作出相应处理。用户理解美乐爱觉平台根据用户请求采取行动需要合理时间,美乐爱觉平台对采取行动前已经产生的后果和损失不承担任何责任。',
'5、用户若发现任何非法使用用户账号或存在安全漏洞的情况,请立即通知美乐爱觉平台工作人员。',
'6、因黑客行为或用户的保管疏忽导致账号非法使用,“美乐爱觉平台”不承担任何责任。'
]
'3、用户不得以任何形式擅自转让或授权他人使用自己的生命力教育联盟平台账户。',
'4、如果用户泄漏了账户密码,或用户发现任何人未经授权使用您的账号和密码的情况,应当立即修改密码并与生命力教育联盟平台客服人员取得联系,并授权生命力教育联盟平台暂停提供服务及展开调查,根据调查结果作出相应处理。用户理解生命力教育联盟平台根据用户请求采取行动需要合理时间,生命力教育联盟平台对采取行动前已经产生的后果和损失不承担任何责任。',
'5、用户若发现任何非法使用用户账号或存在安全漏洞的情况,请立即通知生命力教育联盟平台工作人员。',
'6、因黑客行为或用户的保管疏忽导致账号非法使用,“生命力教育联盟平台”不承担任何责任。',
],
},
{
title: '五、用户声明与保证',
content: [
'1、用户承诺其为具有完全民事行为能力的民事主体,且具有履行本协议约定义务的能力。',
'2、用户保证其为履行本协议而向美乐爱觉平台提供的全部资料均真实、有效。用户有义务在注册时提供自己的真实身份信息,并保证诸如手机号码、姓名等必要身份信息的有效性及安全性,保证美乐爱觉平台工作人员可以通过上述联系方式与用户取得联系。同时,用户也有义务在相关身份信息实际变更时及时更新有关注册资料。',
'3、用户在使用美乐爱觉平台账号或本服务的过程中所制作、上载、复制、发布、传播的任何内容,不得违反国家相关法律制度,包括但不限于:',
'2、用户保证其为履行本协议而向生命力教育联盟平台提供的全部资料均真实、有效。用户有义务在注册时提供自己的真实身份信息,并保证诸如手机号码、姓名等必要身份信息的有效性及安全性,保证生命力教育联盟平台工作人员可以通过上述联系方式与用户取得联系。同时,用户也有义务在相关身份信息实际变更时及时更新有关注册资料。',
'3、用户在使用生命力教育联盟平台账号或本服务的过程中所制作、上载、复制、发布、传播的任何内容,不得违反国家相关法律制度,包括但不限于:',
'(1)反对宪法所确定的基本原则的;',
'(2)危害国家安全,泄露国家秘密,颠覆国家政权,破坏国家统一的;',
'(3)损害国家荣誉和利益的;',
......@@ -253,96 +300,94 @@ const agreementSections = [
'(9)煽动非法集会、结社、游行、示威、聚众扰乱社会秩序的;',
'(10)以非法民间组织名义活动的;',
'(11)含有法律、行政法规禁止的其他内容的。',
'4、用户不得利用美乐爱觉平台账号或本服务制作、上载、复制、发布、传播任何干扰美乐爱觉平台正常运营,以及侵犯其他用户或第三方合法权益的内容,包括但不限于:',
'4、用户不得利用生命力教育联盟平台账号或本服务制作、上载、复制、发布、传播任何干扰生命力教育联盟平台正常运营,以及侵犯其他用户或第三方合法权益的内容,包括但不限于:',
'(1)含有任何性或性暗示的;',
'(2)含有辱骂、恐吓、威胁内容的;',
'(3)含有骚扰、垃圾广告、恶意信息、诱骗信息的;',
'(4)涉及他人隐私、个人信息或资料的;',
'(5)侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的;',
'(6)含有其他干扰本服务正常运营和侵犯其他用户或第三方合法权益内容的信息。',
'5、用户承若对其发表或者上传美乐爱觉平台的所有信息均享有完整的知识产权,或者已经得到相关权利人的合法授权,如用户违反本条规定造成美乐爱觉平台被第三人索赔的,用户应全额赔偿美乐爱觉平台一切损失费用(包括但不限于各种赔偿费、律师代理费、诉讼费及为此支出的其他合理费用)。',
'6、当第三人认为用户发表或上传美乐爱觉平台的信息侵犯其权利,并根据相关法律法规向美乐爱觉平台发送权利通知书时,用户同意美乐爱觉平台可以自行判断决定删除涉嫌侵权信息,除非用户提交书面证据材料排除侵权的可能性,美乐爱觉平台不会自动回复上述删除信息。'
]
'5、用户承若对其发表或者上传生命力教育联盟平台的所有信息均享有完整的知识产权,或者已经得到相关权利人的合法授权,如用户违反本条规定造成生命力教育联盟平台被第三人索赔的,用户应全额赔偿生命力教育联盟平台一切损失费用(包括但不限于各种赔偿费、律师代理费、诉讼费及为此支出的其他合理费用)。',
'6、当第三人认为用户发表或上传生命力教育联盟平台的信息侵犯其权利,并根据相关法律法规向生命力教育联盟平台发送权利通知书时,用户同意生命力教育联盟平台可以自行判断决定删除涉嫌侵权信息,除非用户提交书面证据材料排除侵权的可能性,生命力教育联盟平台不会自动回复上述删除信息。',
],
},
{
title: '六、服务内容',
content: [
'1、美乐爱觉平台具体服务内容由美乐爱觉平台根据实际情况提供,包括但不限于:知识文章、视频课程等。具体以美乐爱觉平台实际提供的功能或服务为准。',
'2、用户认可,美乐爱觉平台发给用户的所有通知、公告及其他消息都可通过用户所提供的联系方式向用户送达或通知。'
]
'1、生命力教育联盟平台具体服务内容由生命力教育联盟平台根据实际情况提供,包括但不限于:知识文章、视频课程等。具体以生命力教育联盟平台实际提供的功能或服务为准。',
'2、用户认可,生命力教育联盟平台发给用户的所有通知、公告及其他消息都可通过用户所提供的联系方式向用户送达或通知。',
],
},
{
title: '七、付费课程相关',
content: [
'美乐爱觉平台提供中华传统国学相关付费视频课程,视频课程属于虚拟计算机软件类商品。根据《消费者权益保护法》第二十五条规定,计算机软件等数字化商品不支持7天无理由退款,故当你购买付费服务成功后,不支持无条件退款。',
'视频课程知识产权归本公司所有,用户付费购买的视频,用户仅享有播放权,不具备视频下载、视频剪辑等、视频传播等其他权利,若未经“杭州南风教育科技有限公司”许可,进行视频下载、视频剪辑等行为,本公司将依法追究其违法行为。'
]
'生命力教育联盟平台提供中华传统国学相关付费视频课程,视频课程属于虚拟计算机软件类商品。根据《消费者权益保护法》第二十五条规定,计算机软件等数字化商品不支持7天无理由退款,故当你购买付费服务成功后,不支持无条件退款。',
'视频课程知识产权归本公司所有,用户付费购买的视频,用户仅享有播放权,不具备视频下载、视频剪辑等、视频传播等其他权利,若未经“杭州南风教育科技有限公司”许可,进行视频下载、视频剪辑等行为,本公司将依法追究其违法行为。',
],
},
{
title: '八、服务的暂停或终止',
content: [
'1、在下列情况下,美乐爱觉平台有权视具体情况自主决定暂停或终止向用户提供服务:',
'(1)在用户违反本服务协议相关规定时,美乐爱觉平台有权视具体情况自主决定暂停或终止向该用户提供服务。',
'(2)如美乐爱觉平台通过用户提供的信息与用户联系时,发现用户在注册时绑定的联系方式已不存在或无法与用户取得联系的,美乐爱觉平台将以系统通知的方式告知用户更改,如用户在三个工作日内仍未能提供新的联系方式,美乐爱觉平台有权终止向该用户提供服务。',
'1、在下列情况下,生命力教育联盟平台有权视具体情况自主决定暂停或终止向用户提供服务:',
'(1)在用户违反本服务协议相关规定时,生命力教育联盟平台有权视具体情况自主决定暂停或终止向该用户提供服务。',
'(2)如生命力教育联盟平台通过用户提供的信息与用户联系时,发现用户在注册时绑定的联系方式已不存在或无法与用户取得联系的,生命力教育联盟平台将以系统通知的方式告知用户更改,如用户在三个工作日内仍未能提供新的联系方式,生命力教育联盟平台有权终止向该用户提供服务。',
'(3)本服务条款终止或更新时,用户明示不愿接受新的服务条款的。',
'(4)用户主动注销账户或用户向美乐爱觉平台申请注销其账户,经美乐爱觉平台审核同意的。',
'(5)其他美乐爱觉平台认为需终止服务的情况。',
'(4)用户主动注销账户或用户向生命力教育联盟平台申请注销其账户,经生命力教育联盟平台审核同意的。',
'(5)其他生命力教育联盟平台认为需终止服务的情况。',
'2、用户理解并同意:',
'(1)服务终止后,美乐爱觉平台没有义务为用户保留原账号中或与之相关的任何信息,或转发任何未曾阅读或发送的信息给用户或第三方。',
'(2)用户在使用本服务期间存在违法行为或违反本协议行为的,美乐爱觉平台仍可依据本协议想用户主张权利或依法依规向行政、司法等机关进行披露。',
'(1)服务终止后,生命力教育联盟平台没有义务为用户保留原账号中或与之相关的任何信息,或转发任何未曾阅读或发送的信息给用户或第三方。',
'(2)用户在使用本服务期间存在违法行为或违反本协议行为的,生命力教育联盟平台仍可依据本协议想用户主张权利或依法依规向行政、司法等机关进行披露。',
'(3)用户在使用本服务期间与其他第三方(如有)之间发生的关系,不因本服务或本协议的终止而终止,第三方仍有权向用户主张权利,用户应继续向第三方履行相关义务。',
'3、美乐爱觉平台保有删除APP内各类不符合法律政策或者不实信息内容而无须通知用户的权利。',
'4、若用户未遵守本协议规定的或其他服务条件的行为,美乐爱觉平台有权作出独立判断并采取暂停或关闭用户账号等措施,对于因此而造成用户无法正常使用账号及相关服务、无法正常获取用户账号内权益的等,美乐爱觉平台不承担任何责任。用户须对自己在网上的言论和行为承担法律责任。对于涉嫌违反法律法规、涉嫌违法犯罪的行为、美乐爱觉平台将保存有关记录,并有权依法向有关主管部门报告、配合有关主管部门调查、向公安机关报案等。'
]
'3、生命力教育联盟平台保有删除APP内各类不符合法律政策或者不实信息内容而无须通知用户的权利。',
'4、若用户未遵守本协议规定的或其他服务条件的行为,生命力教育联盟平台有权作出独立判断并采取暂停或关闭用户账号等措施,对于因此而造成用户无法正常使用账号及相关服务、无法正常获取用户账号内权益的等,生命力教育联盟平台不承担任何责任。用户须对自己在网上的言论和行为承担法律责任。对于涉嫌违反法律法规、涉嫌违法犯罪的行为、生命力教育联盟平台将保存有关记录,并有权依法向有关主管部门报告、配合有关主管部门调查、向公安机关报案等。',
],
},
{
title: '九、服务的变更、中断',
content: [
'1、用户理解并同意,美乐爱觉平台提供的服务是按照现有技术和条件所能达到的现状提供的。用户亦明确知道使用服务存在一定信息风险,美乐爱觉平台将尽力维护用户使用服务的合法权益,但不担保服务一定能满足用户要求,也不担保服务的及时性、安全性、真实性、稳定性、正确性,对用户使用服务中出现的信息(包括但不限于用户发布的信息删除或存储失败),美乐爱觉平台为此不承担任何责任。',
'2、用户理解并同意,美乐爱觉平台为了整体服务运营安全需要或定期或不定期地检测或者更新需要,有权视具体情况决定服务变更、中断、中止或终止服务,但美乐爱觉平台将尽可能事先进行通告。',
'3、用户理解并同意,鉴于网络服务的特殊性,服务可能会受到多种因素(包括但不限于境内外基础运营商的网络故障、技术缺陷、覆盖范围限制、不可抗力、计算机病毒、黑客攻击或其他非美乐爱觉平台技术能力范围内的事因等)的影响或干扰,美乐爱觉平台不能随时或始终预见和防范上述因素造成的服务中断、用户存储信息丢失、未保存、出现乱码、错误接收、无法接收、延迟接收等,美乐爱觉平台无需为此对任何用户或任何第三方承担责任。若发生上述因此,美乐爱觉平台将尽可能及时通过公告、系统通知等其他合理方式通知受到影响的用户。'
]
'1、用户理解并同意,生命力教育联盟平台提供的服务是按照现有技术和条件所能达到的现状提供的。用户亦明确知道使用服务存在一定信息风险,生命力教育联盟平台将尽力维护用户使用服务的合法权益,但不担保服务一定能满足用户要求,也不担保服务的及时性、安全性、真实性、稳定性、正确性,对用户使用服务中出现的信息(包括但不限于用户发布的信息删除或存储失败),生命力教育联盟平台为此不承担任何责任。',
'2、用户理解并同意,生命力教育联盟平台为了整体服务运营安全需要或定期或不定期地检测或者更新需要,有权视具体情况决定服务变更、中断、中止或终止服务,但生命力教育联盟平台将尽可能事先进行通告。',
'3、用户理解并同意,鉴于网络服务的特殊性,服务可能会受到多种因素(包括但不限于境内外基础运营商的网络故障、技术缺陷、覆盖范围限制、不可抗力、计算机病毒、黑客攻击或其他非生命力教育联盟平台技术能力范围内的事因等)的影响或干扰,生命力教育联盟平台不能随时或始终预见和防范上述因素造成的服务中断、用户存储信息丢失、未保存、出现乱码、错误接收、无法接收、延迟接收等,生命力教育联盟平台无需为此对任何用户或任何第三方承担责任。若发生上述因此,生命力教育联盟平台将尽可能及时通过公告、系统通知等其他合理方式通知受到影响的用户。',
],
},
{
title: '十、知识产权条款',
content: [
'1、北京美乐爱觉有限公司对本服务中所有内容,包括但不限于设计、画面安排、软件架构、图片、文章、视频等均由北京美乐爱觉有限公司依法拥有其知识产权(包括但不限于商标权、专利权、著作权、商业秘密等)。',
'2、非经北京美乐爱觉有限公司书面同意,任何人不得擅自使用、修改、复制、公开传播、改变、散布、发行或公开发表美乐爱觉平台程序或内容。',
'3、尊重知识产权是用户应尽的义务,如有违反,应承担相应赔偿责任。'
]
'1、北京生命力教育联盟有限公司对本服务中所有内容,包括但不限于设计、画面安排、软件架构、图片、文章、视频等均由北京生命力教育联盟有限公司依法拥有其知识产权(包括但不限于商标权、专利权、著作权、商业秘密等)。',
'2、非经北京生命力教育联盟有限公司书面同意,任何人不得擅自使用、修改、复制、公开传播、改变、散布、发行或公开发表生命力教育联盟平台程序或内容。',
'3、尊重知识产权是用户应尽的义务,如有违反,应承担相应赔偿责任。',
],
},
{
title: '十一、服务条款修改',
content: [
'1、美乐爱觉平台有权随时修改本服务条款的任何内容,一旦本服务条款的任何内容发生变动,美乐爱觉平台将会通过适当方式(包括但不限于弹窗)向用户提示修改内容。',
'2、如果不同意美乐爱觉平台对本服务条款所做的修改,用户有权停止使用本服务。',
'3、如果用户继续使用网络服务,则视为用户接受美乐爱觉平台对本服务条款所做的修改。'
]
'1、生命力教育联盟平台有权随时修改本服务条款的任何内容,一旦本服务条款的任何内容发生变动,生命力教育联盟平台将会通过适当方式(包括但不限于弹窗)向用户提示修改内容。',
'2、如果不同意生命力教育联盟平台对本服务条款所做的修改,用户有权停止使用本服务。',
'3、如果用户继续使用网络服务,则视为用户接受生命力教育联盟平台对本服务条款所做的修改。',
],
},
{
title: '十二、隐私保护',
content: [
'请阅读《隐私保护政策》。'
]
content: ['请阅读《隐私保护政策》。'],
},
{
title: '十三、其他',
content: [
'1、若美乐爱觉平台已经明示其服务提供方式发生变更并提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。',
'2、用户同意保障和维护美乐爱觉平台及其他用户的利益,由于用户在使用美乐爱觉平台有违法、不真实、不正当、侵犯第三方合法权益的行为,或用户违反本协议项下的任何条款而给美乐爱觉平台及任何其他第三方造成损失,用户同意承担由此造成的损害赔偿责任。',
'3、本协议的效力、解释及纠纷的解决,适用于中华人民共和国法律。若用户和美乐爱觉平台之间发生任何纠纷或争议,首先应友好协商解决,协商不成的,任何一方均有权向杭州南风教育科技有限公司住所地人民法院提起诉讼。',
'1、若生命力教育联盟平台已经明示其服务提供方式发生变更并提醒用户应当注意事项,用户未按要求操作所产生的一切后果由用户自行承担。',
'2、用户同意保障和维护生命力教育联盟平台及其他用户的利益,由于用户在使用生命力教育联盟平台有违法、不真实、不正当、侵犯第三方合法权益的行为,或用户违反本协议项下的任何条款而给生命力教育联盟平台及任何其他第三方造成损失,用户同意承担由此造成的损害赔偿责任。',
'3、本协议的效力、解释及纠纷的解决,适用于中华人民共和国法律。若用户和生命力教育联盟平台之间发生任何纠纷或争议,首先应友好协商解决,协商不成的,任何一方均有权向杭州南风教育科技有限公司住所地人民法院提起诉讼。',
'4、本协议的任何条款被司法部门认定为无效或不具可执行性,或违反所须适用的法律的,则该条款将被视为无效,其余条款仍然有效,对双方具有约束力。',
'5、本协议最终解释权归杭州南风教育科技有限公司所有。'
]
'5、本协议最终解释权归杭州南风教育科技有限公司所有。',
],
},
{
title: '十四、免责声明',
content: [
'1、用户明确同意其使用美乐爱觉平台服务,以及对美乐爱觉平台服务加以依赖所存在的全部责任和风险将完全有其自己承担,因其使用美乐爱觉平台服务过程的行为,以及因此而产生的一切后果也由其自己承担,美乐爱觉平台对用户不承担任何责任。',
'2、美乐爱觉平台不担保服务一定能满足用户要求,也不担保服务不会中断,对服务的及时性、安全性、准确性也都不作担保。',
'3、用户因使用本服务而产生的任何间接的、附带的、特殊的、结果性的或惩戒性的损害,美乐爱觉平台概不负责,均由用户自行承担。'
]
}
'1、用户明确同意其使用生命力教育联盟平台服务,以及对生命力教育联盟平台服务加以依赖所存在的全部责任和风险将完全有其自己承担,因其使用生命力教育联盟平台服务过程的行为,以及因此而产生的一切后果也由其自己承担,生命力教育联盟平台对用户不承担任何责任。',
'2、生命力教育联盟平台不担保服务一定能满足用户要求,也不担保服务不会中断,对服务的及时性、安全性、准确性也都不作担保。',
'3、用户因使用本服务而产生的任何间接的、附带的、特殊的、结果性的或惩戒性的损害,生命力教育联盟平台概不负责,均由用户自行承担。',
],
},
]
</script>
......