chore: 将品牌名称从“美乐爱觉”更新为“生命力教育联盟”
更新所有页面、组件和文档中的品牌名称,包括用户协议、页面标题、分享标题、海报文案等,以反映品牌名称变更
Showing
13 changed files
with
204 additions
and
155 deletions
| ... | @@ -12,35 +12,49 @@ | ... | @@ -12,35 +12,49 @@ |
| 12 | position="bottom" | 12 | position="bottom" |
| 13 | :style="{ height: '100%' }" | 13 | :style="{ height: '100%' }" |
| 14 | > | 14 | > |
| 15 | - <div class="flex flex-col h-full relative"> | 15 | + <div class="relative flex h-full flex-col"> |
| 16 | - <div class="sticky top-0 flex justify-between items-center p-4 border-b border-gray-100 bg-white z-10"> | 16 | + <div |
| 17 | - <h3 class="font-medium text-lg">{{ title }}</h3> | 17 | + class="sticky top-0 z-10 flex items-center justify-between border-b border-gray-100 bg-white p-4" |
| 18 | + > | ||
| 19 | + <h3 class="text-lg font-medium">{{ title }}</h3> | ||
| 18 | <van-icon name="cross" @click="$emit('update:show', false)" class="text-gray-500" /> | 20 | <van-icon name="cross" @click="$emit('update:show', false)" class="text-gray-500" /> |
| 19 | </div> | 21 | </div> |
| 20 | 22 | ||
| 21 | <div class="flex-1 overflow-y-auto p-4 pt-0"> | 23 | <div class="flex-1 overflow-y-auto p-4 pt-0"> |
| 22 | <div v-if="type === 'terms'" class="space-y-4 text-gray-600"> | 24 | <div v-if="type === 'terms'" class="space-y-4 text-gray-600"> |
| 23 | - <p>欢迎使用美乐爱觉教育!在使用我们的服务之前,请仔细阅读以下用户协议。</p> | 25 | + <p>欢迎使用生命力教育联盟教育!在使用我们的服务之前,请仔细阅读以下用户协议。</p> |
| 24 | 26 | ||
| 25 | <h5 class="font-medium text-gray-800">1. 服务内容</h5> | 27 | <h5 class="font-medium text-gray-800">1. 服务内容</h5> |
| 26 | - <p>美乐爱觉教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。</p> | 28 | + <p> |
| 29 | + 生命力教育联盟教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。 | ||
| 30 | + </p> | ||
| 27 | 31 | ||
| 28 | <h5 class="font-medium text-gray-800">2. 用户责任</h5> | 32 | <h5 class="font-medium text-gray-800">2. 用户责任</h5> |
| 29 | - <p>用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。</p> | 33 | + <p> |
| 34 | + 用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。 | ||
| 35 | + </p> | ||
| 30 | 36 | ||
| 31 | <h5 class="font-medium text-gray-800">3. 知识产权</h5> | 37 | <h5 class="font-medium text-gray-800">3. 知识产权</h5> |
| 32 | - <p>本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。</p> | 38 | + <p> |
| 39 | + 本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。 | ||
| 40 | + </p> | ||
| 33 | 41 | ||
| 34 | <h5 class="font-medium text-gray-800">4. 免责声明</h5> | 42 | <h5 class="font-medium text-gray-800">4. 免责声明</h5> |
| 35 | <p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p> | 43 | <p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p> |
| 36 | <h5 class="font-medium text-gray-800">1. 服务内容</h5> | 44 | <h5 class="font-medium text-gray-800">1. 服务内容</h5> |
| 37 | - <p>美乐爱觉教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。</p> | 45 | + <p> |
| 46 | + 生命力教育联盟教育为用户提供在线教育、活动报名等服务。我们保留随时修改或中断服务的权利,而无需事先通知用户。 | ||
| 47 | + </p> | ||
| 38 | 48 | ||
| 39 | <h5 class="font-medium text-gray-800">2. 用户责任</h5> | 49 | <h5 class="font-medium text-gray-800">2. 用户责任</h5> |
| 40 | - <p>用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。</p> | 50 | + <p> |
| 51 | + 用户在使用本服务时必须遵守所有适用的法律法规。用户承诺提供真实、准确、完整的个人信息。 | ||
| 52 | + </p> | ||
| 41 | 53 | ||
| 42 | <h5 class="font-medium text-gray-800">3. 知识产权</h5> | 54 | <h5 class="font-medium text-gray-800">3. 知识产权</h5> |
| 43 | - <p>本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。</p> | 55 | + <p> |
| 56 | + 本平台的所有内容,包括但不限于文字、图片、音频、视频等,均受著作权法和其他知识产权法律法规的保护。 | ||
| 57 | + </p> | ||
| 44 | 58 | ||
| 45 | <h5 class="font-medium text-gray-800">4. 免责声明</h5> | 59 | <h5 class="font-medium text-gray-800">4. 免责声明</h5> |
| 46 | <p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p> | 60 | <p>对于因不可抗力或非本平台原因造成的服务中断或其他缺陷,本平台不承担任何责任。</p> |
| ... | @@ -50,10 +64,14 @@ | ... | @@ -50,10 +64,14 @@ |
| 50 | <p>我们重视您的隐私保护。本隐私政策说明我们如何收集、使用和保护您的个人信息。</p> | 64 | <p>我们重视您的隐私保护。本隐私政策说明我们如何收集、使用和保护您的个人信息。</p> |
| 51 | 65 | ||
| 52 | <h5 class="font-medium text-gray-800">1. 信息收集</h5> | 66 | <h5 class="font-medium text-gray-800">1. 信息收集</h5> |
| 53 | - <p>我们收集的信息包括但不限于:姓名、联系方式、位置信息等。这些信息用于提供更好的服务体验。</p> | 67 | + <p> |
| 68 | + 我们收集的信息包括但不限于:姓名、联系方式、位置信息等。这些信息用于提供更好的服务体验。 | ||
| 69 | + </p> | ||
| 54 | 70 | ||
| 55 | <h5 class="font-medium text-gray-800">2. 信息使用</h5> | 71 | <h5 class="font-medium text-gray-800">2. 信息使用</h5> |
| 56 | - <p>我们承诺对您的个人信息进行严格保密,不会将其出售、出租或以其他方式泄露给任何第三方。</p> | 72 | + <p> |
| 73 | + 我们承诺对您的个人信息进行严格保密,不会将其出售、出租或以其他方式泄露给任何第三方。 | ||
| 74 | + </p> | ||
| 57 | 75 | ||
| 58 | <h5 class="font-medium text-gray-800">3. 信息安全</h5> | 76 | <h5 class="font-medium text-gray-800">3. 信息安全</h5> |
| 59 | <p>我们采用行业标准的安全措施保护您的个人信息,防止未经授权的访问、使用或泄露。</p> | 77 | <p>我们采用行业标准的安全措施保护您的个人信息,防止未经授权的访问、使用或泄露。</p> |
| ... | @@ -72,19 +90,19 @@ defineProps({ | ... | @@ -72,19 +90,19 @@ defineProps({ |
| 72 | show: { | 90 | show: { |
| 73 | type: Boolean, | 91 | type: Boolean, |
| 74 | required: true, | 92 | required: true, |
| 75 | - default: false | 93 | + default: false, |
| 76 | }, | 94 | }, |
| 77 | /** 协议类型: 'terms' | 'privacy' */ | 95 | /** 协议类型: 'terms' | 'privacy' */ |
| 78 | type: { | 96 | type: { |
| 79 | type: String, | 97 | type: String, |
| 80 | required: true, | 98 | required: true, |
| 81 | - validator: (value) => ['terms', 'privacy'].includes(value) | 99 | + validator: value => ['terms', 'privacy'].includes(value), |
| 82 | }, | 100 | }, |
| 83 | /** 弹窗标题 */ | 101 | /** 弹窗标题 */ |
| 84 | title: { | 102 | title: { |
| 85 | type: String, | 103 | type: String, |
| 86 | - required: true | 104 | + required: true, |
| 87 | - } | 105 | + }, |
| 88 | }) | 106 | }) |
| 89 | 107 | ||
| 90 | defineEmits(['update:show']) | 108 | defineEmits(['update:show']) | ... | ... |
| ... | @@ -9,69 +9,81 @@ | ... | @@ -9,69 +9,81 @@ |
| 9 | * @Description: 用户协议组件 | 9 | * @Description: 用户协议组件 |
| 10 | --> | 10 | --> |
| 11 | <template> | 11 | <template> |
| 12 | - <van-popup | 12 | + <van-popup v-model:show="show" round position="bottom" :style="{ height: '90%' }" teleport="body"> |
| 13 | - v-model:show="show" | ||
| 14 | - round | ||
| 15 | - position="bottom" | ||
| 16 | - :style="{ height: '90%' }" | ||
| 17 | - teleport="body" | ||
| 18 | - > | ||
| 19 | <div class="p-4"> | 13 | <div class="p-4"> |
| 20 | - <div class="text-xl font-bold text-center mb-4">美乐爱觉宇宙用户协议</div> | 14 | + <div class="mb-4 text-center text-xl font-bold">生命力教育联盟宇宙用户协议</div> |
| 21 | - <div class="agreement-content overflow-y-auto h-[calc(100vh*0.8-120px)] px-2"> | 15 | + <div class="agreement-content h-[calc(100vh*0.8-120px)] overflow-y-auto px-2"> |
| 22 | - <h2 class="text-lg font-semibold mb-3">1. 协议的范围</h2> | 16 | + <h2 class="mb-3 text-lg font-semibold">1. 协议的范围</h2> |
| 23 | - <p class="mb-4 text-gray-700">欢迎您使用美乐爱觉宇宙平台服务!为使用美乐爱觉宇宙平台服务,您应当阅读并遵守本《用户协议》。请您务必审慎阅读、充分理解各条款内容。</p> | 17 | + <p class="mb-4 text-gray-700"> |
| 18 | + 欢迎您使用生命力教育联盟宇宙平台服务!为使用生命力教育联盟宇宙平台服务,您应当阅读并遵守本《用户协议》。请您务必审慎阅读、充分理解各条款内容。 | ||
| 19 | + </p> | ||
| 24 | 20 | ||
| 25 | - <h2 class="text-lg font-semibold mb-3">2. 账号注册</h2> | 21 | + <h2 class="mb-3 text-lg font-semibold">2. 账号注册</h2> |
| 26 | - <p class="mb-4 text-gray-700">您在使用本服务前需要注册一个美乐爱觉宇宙账号。美乐爱觉宇宙账号应当使用手机号码绑定注册,请您使用尚未与美乐爱觉宇宙账号绑定的手机号码,以及未被平台根据本协议封禁的手机号码注册。</p> | 22 | + <p class="mb-4 text-gray-700"> |
| 23 | + 您在使用本服务前需要注册一个生命力教育联盟宇宙账号。生命力教育联盟宇宙账号应当使用手机号码绑定注册,请您使用尚未与生命力教育联盟宇宙账号绑定的手机号码,以及未被平台根据本协议封禁的手机号码注册。 | ||
| 24 | + </p> | ||
| 27 | 25 | ||
| 28 | - <h2 class="text-lg font-semibold mb-3">3. 用户个人信息保护</h2> | 26 | + <h2 class="mb-3 text-lg font-semibold">3. 用户个人信息保护</h2> |
| 29 | - <p class="mb-4 text-gray-700">我们非常重视用户个人信息的保护,保护用户个人信息是我们的基本原则之一。我们将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可我们不会向第三方公开、透露用户个人信息。</p> | 27 | + <p class="mb-4 text-gray-700"> |
| 28 | + 我们非常重视用户个人信息的保护,保护用户个人信息是我们的基本原则之一。我们将会采取合理的措施保护用户的个人信息。除法律法规规定的情形外,未经用户许可我们不会向第三方公开、透露用户个人信息。 | ||
| 29 | + </p> | ||
| 30 | 30 | ||
| 31 | - <h2 class="text-lg font-semibold mb-3">4. 内容规范</h2> | 31 | + <h2 class="mb-3 text-lg font-semibold">4. 内容规范</h2> |
| 32 | - <p class="mb-4 text-gray-700">您在使用本服务时需要遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性等七条底线。</p> | 32 | + <p class="mb-4 text-gray-700"> |
| 33 | + 您在使用本服务时需要遵守法律法规、社会主义制度、国家利益、公民合法权益、公共秩序、社会道德风尚和信息真实性等七条底线。 | ||
| 34 | + </p> | ||
| 33 | 35 | ||
| 34 | - <h2 class="text-lg font-semibold mb-3">5. 知识产权</h2> | 36 | + <h2 class="mb-3 text-lg font-semibold">5. 知识产权</h2> |
| 35 | - <p class="mb-4 text-gray-700">美乐爱觉宇宙平台所包含的全部智力成果,包括但不限于平台内容、平台设计、源代码等,均属于平台所有。未经平台许可,任何人不得擅自使用。</p> | 37 | + <p class="mb-4 text-gray-700"> |
| 38 | + 生命力教育联盟宇宙平台所包含的全部智力成果,包括但不限于平台内容、平台设计、源代码等,均属于平台所有。未经平台许可,任何人不得擅自使用。 | ||
| 39 | + </p> | ||
| 36 | 40 | ||
| 37 | - <h2 class="text-lg font-semibold mb-3">6. 服务的变更、中断和终止</h2> | 41 | + <h2 class="mb-3 text-lg font-semibold">6. 服务的变更、中断和终止</h2> |
| 38 | - <p class="mb-4 text-gray-700">我们可能会对服务内容进行变更,也可能会中断、中止或终止服务。对于付费服务,我们会在变更前通知您,并向您提供退款等必要的补偿。</p> | 42 | + <p class="mb-4 text-gray-700"> |
| 43 | + 我们可能会对服务内容进行变更,也可能会中断、中止或终止服务。对于付费服务,我们会在变更前通知您,并向您提供退款等必要的补偿。 | ||
| 44 | + </p> | ||
| 39 | 45 | ||
| 40 | - <h2 class="text-lg font-semibold mb-3">7. 违约处理</h2> | 46 | + <h2 class="mb-3 text-lg font-semibold">7. 违约处理</h2> |
| 41 | - <p class="mb-4 text-gray-700">如果您违反本协议约定,我们有权视情况采取预先警示、限制或禁止使用全部或部分服务功能、封禁账号等措施。</p> | 47 | + <p class="mb-4 text-gray-700"> |
| 48 | + 如果您违反本协议约定,我们有权视情况采取预先警示、限制或禁止使用全部或部分服务功能、封禁账号等措施。 | ||
| 49 | + </p> | ||
| 42 | 50 | ||
| 43 | - <h2 class="text-lg font-semibold mb-3">8. 其他条款</h2> | 51 | + <h2 class="mb-3 text-lg font-semibold">8. 其他条款</h2> |
| 44 | - <p class="mb-4 text-gray-700">本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。如果本协议中任何一条被视为废止、无效或不可执行,应视为可分的且并不影响任何其余条款的有效性和可执行性。</p> | 52 | + <p class="mb-4 text-gray-700"> |
| 53 | + 本协议所有条款的标题仅为阅读方便,本身并无实际涵义,不能作为本协议涵义解释的依据。如果本协议中任何一条被视为废止、无效或不可执行,应视为可分的且并不影响任何其余条款的有效性和可执行性。 | ||
| 54 | + </p> | ||
| 45 | </div> | 55 | </div> |
| 46 | - <div class="flex justify-center mt-4"> | 56 | + <div class="mt-4 flex justify-center"> |
| 47 | - <van-button round type="primary" color="#4CAF50" block @click="handleClose">我已阅读并同意</van-button> | 57 | + <van-button round type="primary" color="#4CAF50" block @click="handleClose" |
| 58 | + >我已阅读并同意</van-button | ||
| 59 | + > | ||
| 48 | </div> | 60 | </div> |
| 49 | </div> | 61 | </div> |
| 50 | </van-popup> | 62 | </van-popup> |
| 51 | </template> | 63 | </template> |
| 52 | 64 | ||
| 53 | <script setup> | 65 | <script setup> |
| 54 | -import { ref, defineExpose } from 'vue'; | 66 | +import { ref, defineExpose } from 'vue' |
| 55 | 67 | ||
| 56 | -const show = ref(false); | 68 | +const show = ref(false) |
| 57 | 69 | ||
| 58 | /** | 70 | /** |
| 59 | * @description 关闭协议弹窗 | 71 | * @description 关闭协议弹窗 |
| 60 | */ | 72 | */ |
| 61 | const handleClose = () => { | 73 | const handleClose = () => { |
| 62 | - show.value = false; | 74 | + show.value = false |
| 63 | -}; | 75 | +} |
| 64 | 76 | ||
| 65 | /** | 77 | /** |
| 66 | * @description 打开协议弹窗 | 78 | * @description 打开协议弹窗 |
| 67 | */ | 79 | */ |
| 68 | const openAgreement = () => { | 80 | const openAgreement = () => { |
| 69 | - show.value = true; | 81 | + show.value = true |
| 70 | -}; | 82 | +} |
| 71 | 83 | ||
| 72 | defineExpose({ | 84 | defineExpose({ |
| 73 | - openAgreement | 85 | + openAgreement, |
| 74 | -}); | 86 | +}) |
| 75 | </script> | 87 | </script> |
| 76 | 88 | ||
| 77 | <style lang="less" scoped> | 89 | <style lang="less" scoped> | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | - <div class="recall-poster-container w-full max-w-[340px] relative select-none shrink-0 my-auto mx-auto"> | 2 | + <div |
| 3 | + class="recall-poster-container relative mx-auto my-auto w-full max-w-[340px] shrink-0 select-none" | ||
| 4 | + > | ||
| 3 | <!-- 最终生成的海报图片展示区域 --> | 5 | <!-- 最终生成的海报图片展示区域 --> |
| 4 | - <div v-if="posterImgSrc" class="relative w-full fade-in"> | 6 | + <div v-if="posterImgSrc" class="fade-in relative w-full"> |
| 5 | - <img :src="posterImgSrc" class="w-full h-auto rounded-2xl shadow-2xl block" alt="分享海报" /> | 7 | + <img :src="posterImgSrc" class="block h-auto w-full rounded-2xl shadow-2xl" alt="分享海报" /> |
| 6 | - <div class="text-white/80 text-center text-xs mt-4">长按图片保存</div> | 8 | + <div class="mt-4 text-center text-xs text-white/80">长按图片保存</div> |
| 7 | </div> | 9 | </div> |
| 8 | 10 | ||
| 9 | <!-- 生成中/加载中占位 --> | 11 | <!-- 生成中/加载中占位 --> |
| 10 | - <div v-else | 12 | + <div |
| 11 | - 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"> | 13 | + v-else |
| 14 | + 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" | ||
| 15 | + > | ||
| 12 | <van-loading type="spinner" color="#ffffff" size="32px" /> | 16 | <van-loading type="spinner" color="#ffffff" size="32px" /> |
| 13 | <div class="mt-4 text-sm font-medium">海报生成中...</div> | 17 | <div class="mt-4 text-sm font-medium">海报生成中...</div> |
| 14 | </div> | 18 | </div> |
| ... | @@ -27,31 +31,31 @@ const props = defineProps({ | ... | @@ -27,31 +31,31 @@ const props = defineProps({ |
| 27 | /** 海报背景图片 URL */ | 31 | /** 海报背景图片 URL */ |
| 28 | bgUrl: { | 32 | bgUrl: { |
| 29 | type: String, | 33 | type: String, |
| 30 | - required: true | 34 | + required: true, |
| 31 | }, | 35 | }, |
| 32 | /** 海报标题 */ | 36 | /** 海报标题 */ |
| 33 | title: { | 37 | title: { |
| 34 | type: String, | 38 | type: String, |
| 35 | - default: '' | 39 | + default: '', |
| 36 | }, | 40 | }, |
| 37 | /** Logo 图片 URL */ | 41 | /** Logo 图片 URL */ |
| 38 | logoUrl: { | 42 | logoUrl: { |
| 39 | type: String, | 43 | type: String, |
| 40 | - default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png' | 44 | + default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/kai@2x.png', |
| 41 | }, | 45 | }, |
| 42 | /** 二维码图片 URL */ | 46 | /** 二维码图片 URL */ |
| 43 | qrUrl: { | 47 | qrUrl: { |
| 44 | type: String, | 48 | type: String, |
| 45 | - default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/%E4%BA%8C%E7%BB%B4%E7%A0%81@2x.png' | 49 | + default: 'https://cdn.ipadbiz.cn/mlaj/recall/poster/%E4%BA%8C%E7%BB%B4%E7%A0%81@2x.png', |
| 46 | - } | 50 | + }, |
| 47 | }) | 51 | }) |
| 48 | 52 | ||
| 49 | const canvasRef = ref(null) | 53 | const canvasRef = ref(null) |
| 50 | const posterImgSrc = ref('') | 54 | const posterImgSrc = ref('') |
| 51 | 55 | ||
| 52 | // 工具函数:加载图片 | 56 | // 工具函数:加载图片 |
| 53 | -const loadImage = (src) => { | 57 | +const loadImage = src => |
| 54 | - return new Promise((resolve, reject) => { | 58 | + new Promise((resolve, reject) => { |
| 55 | if (!src) { | 59 | if (!src) { |
| 56 | reject(new Error('Image source is empty')) | 60 | reject(new Error('Image source is empty')) |
| 57 | return | 61 | return |
| ... | @@ -62,7 +66,7 @@ const loadImage = (src) => { | ... | @@ -62,7 +66,7 @@ const loadImage = (src) => { |
| 62 | img.crossOrigin = 'anonymous' | 66 | img.crossOrigin = 'anonymous' |
| 63 | } | 67 | } |
| 64 | img.onload = () => resolve(img) | 68 | img.onload = () => resolve(img) |
| 65 | - img.onerror = (e) => { | 69 | + img.onerror = e => { |
| 66 | console.error('Failed to load image:', src) | 70 | console.error('Failed to load image:', src) |
| 67 | // 图片加载失败不应该阻断流程,返回 null 或者透明图占位 | 71 | // 图片加载失败不应该阻断流程,返回 null 或者透明图占位 |
| 68 | // 这里resolve null,绘制时跳过 | 72 | // 这里resolve null,绘制时跳过 |
| ... | @@ -70,7 +74,6 @@ const loadImage = (src) => { | ... | @@ -70,7 +74,6 @@ const loadImage = (src) => { |
| 70 | } | 74 | } |
| 71 | img.src = src | 75 | img.src = src |
| 72 | }) | 76 | }) |
| 73 | -} | ||
| 74 | 77 | ||
| 75 | // 工具函数:绘制圆角矩形 | 78 | // 工具函数:绘制圆角矩形 |
| 76 | const drawRoundedRect = (ctx, x, y, width, height, radius) => { | 79 | const drawRoundedRect = (ctx, x, y, width, height, radius) => { |
| ... | @@ -170,7 +173,10 @@ const generatePoster = async () => { | ... | @@ -170,7 +173,10 @@ const generatePoster = async () => { |
| 170 | let qrUrlToLoad = props.qrUrl | 173 | let qrUrlToLoad = props.qrUrl |
| 171 | let qrBlobUrl = null | 174 | let qrBlobUrl = null |
| 172 | // 兼容完整 URL 和相对路径,只要包含特定特征或是相对路径 | 175 | // 兼容完整 URL 和相对路径,只要包含特定特征或是相对路径 |
| 173 | - if (qrUrlToLoad && (qrUrlToLoad.startsWith('/') || qrUrlToLoad.includes('m=srv') || qrUrlToLoad.includes('http'))) { | 176 | + if ( |
| 177 | + qrUrlToLoad && | ||
| 178 | + (qrUrlToLoad.startsWith('/') || qrUrlToLoad.includes('m=srv') || qrUrlToLoad.includes('http')) | ||
| 179 | + ) { | ||
| 174 | try { | 180 | try { |
| 175 | // 使用 fetch 替代 axios,避免拦截器自动添加 header 导致 CORS 预检失败 | 181 | // 使用 fetch 替代 axios,避免拦截器自动添加 header 导致 CORS 预检失败 |
| 176 | const res = await fetch(qrUrlToLoad) | 182 | const res = await fetch(qrUrlToLoad) |
| ... | @@ -188,7 +194,7 @@ const generatePoster = async () => { | ... | @@ -188,7 +194,7 @@ const generatePoster = async () => { |
| 188 | const [bgImg, logoImg, qrImg] = await Promise.all([ | 194 | const [bgImg, logoImg, qrImg] = await Promise.all([ |
| 189 | loadImage(props.bgUrl), | 195 | loadImage(props.bgUrl), |
| 190 | loadImage(props.logoUrl), | 196 | loadImage(props.logoUrl), |
| 191 | - loadImage(qrUrlToLoad) | 197 | + loadImage(qrUrlToLoad), |
| 192 | ]) | 198 | ]) |
| 193 | 199 | ||
| 194 | // 清理 Blob URL | 200 | // 清理 Blob URL |
| ... | @@ -250,13 +256,13 @@ const generatePoster = async () => { | ... | @@ -250,13 +256,13 @@ const generatePoster = async () => { |
| 250 | ctx.shadowOffsetY = 1 * scale | 256 | ctx.shadowOffsetY = 1 * scale |
| 251 | 257 | ||
| 252 | // Column 1: "每一段成长故事" (左边那列,离右边远一点) | 258 | // Column 1: "每一段成长故事" (左边那列,离右边远一点) |
| 253 | - // Column 2: "见证我在美乐爱觉宇宙的" (右边那列) | 259 | + // Column 2: "见证我在生命力教育联盟宇宙的" (右边那列) |
| 254 | 260 | ||
| 255 | - const colRightX = width - textRightMargin - (fontSize / 2) | 261 | + const colRightX = width - textRightMargin - fontSize / 2 |
| 256 | const colLeftX = colRightX - fontSize - 12 * scale // gap-3 = 12px | 262 | const colLeftX = colRightX - fontSize - 12 * scale // gap-3 = 12px |
| 257 | 263 | ||
| 258 | - const textRight = "见证我在美乐爱觉宇宙的" | 264 | + const textRight = '见证我在生命力教育联盟宇宙的' |
| 259 | - const textLeft = "每一段成长故事" | 265 | + const textLeft = '每一段成长故事' |
| 260 | 266 | ||
| 261 | // 计算文本对齐 | 267 | // 计算文本对齐 |
| 262 | // 目标:整个文本块的底部与 bottom-6 对齐 | 268 | // 目标:整个文本块的底部与 bottom-6 对齐 |
| ... | @@ -294,7 +300,6 @@ const generatePoster = async () => { | ... | @@ -294,7 +300,6 @@ const generatePoster = async () => { |
| 294 | 300 | ||
| 295 | ctx.shadowColor = 'transparent' | 301 | ctx.shadowColor = 'transparent' |
| 296 | 302 | ||
| 297 | - | ||
| 298 | // 6. 绘制 Info Area 内容 | 303 | // 6. 绘制 Info Area 内容 |
| 299 | // 坐标参考 | 304 | // 坐标参考 |
| 300 | const infoY = imgAreaHeight | 305 | const infoY = imgAreaHeight |
| ... | @@ -313,7 +318,7 @@ const generatePoster = async () => { | ... | @@ -313,7 +318,7 @@ const generatePoster = async () => { |
| 313 | const titleX = padding | 318 | const titleX = padding |
| 314 | const titleY = infoY + padding | 319 | const titleY = infoY + padding |
| 315 | const qrSectionW = 85 * scale // approx | 320 | const qrSectionW = 85 * scale // approx |
| 316 | - const titleMaxW = width - padding - qrSectionW - (10 * scale) // extra gap | 321 | + const titleMaxW = width - padding - qrSectionW - 10 * scale // extra gap |
| 317 | 322 | ||
| 318 | ctx.fillStyle = '#0052D9' | 323 | ctx.fillStyle = '#0052D9' |
| 319 | ctx.font = `bold ${15 * scale}px sans-serif` | 324 | ctx.font = `bold ${15 * scale}px sans-serif` |
| ... | @@ -331,7 +336,7 @@ const generatePoster = async () => { | ... | @@ -331,7 +336,7 @@ const generatePoster = async () => { |
| 331 | // Section starts at width - padding - qrSectionW? | 336 | // Section starts at width - padding - qrSectionW? |
| 332 | // Actually DOM is flex justify-between. | 337 | // Actually DOM is flex justify-between. |
| 333 | // QR is at the very right (minus padding). | 338 | // QR is at the very right (minus padding). |
| 334 | - const qrX = width - padding - qrSize + (4 * scale) // slight adjustment | 339 | + const qrX = width - padding - qrSize + 4 * scale // slight adjustment |
| 335 | const qrY = infoY + padding | 340 | const qrY = infoY + padding |
| 336 | 341 | ||
| 337 | ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize) | 342 | ctx.drawImage(qrImg, qrX, qrY, qrSize, qrSize) |
| ... | @@ -345,24 +350,26 @@ const generatePoster = async () => { | ... | @@ -345,24 +350,26 @@ const generatePoster = async () => { |
| 345 | ctx.textAlign = 'center' | 350 | ctx.textAlign = 'center' |
| 346 | 351 | ||
| 347 | const textCenterX = qrX + qrSize / 2 | 352 | const textCenterX = qrX + qrSize / 2 |
| 348 | - const textStartY = qrY + qrSize + (8 * scale) // mb-2 is for img | 353 | + const textStartY = qrY + qrSize + 8 * scale // mb-2 is for img |
| 349 | 354 | ||
| 350 | - ctx.fillText("跟我一起加入", textCenterX, textStartY) | 355 | + ctx.fillText('跟我一起加入', textCenterX, textStartY) |
| 351 | - ctx.fillText("美乐爱觉宇宙吧", textCenterX, textStartY + smallTextSize * 1.4) | 356 | + ctx.fillText('生命力教育联盟宇宙吧', textCenterX, textStartY + smallTextSize * 1.4) |
| 352 | } | 357 | } |
| 353 | 358 | ||
| 354 | // 7. 导出图片 | 359 | // 7. 导出图片 |
| 355 | posterImgSrc.value = canvas.toDataURL('image/png') | 360 | posterImgSrc.value = canvas.toDataURL('image/png') |
| 356 | - | ||
| 357 | } catch (error) { | 361 | } catch (error) { |
| 358 | console.error('Canvas poster generation failed:', error) | 362 | console.error('Canvas poster generation failed:', error) |
| 359 | showToast('生成失败,请重试') | 363 | showToast('生成失败,请重试') |
| 360 | } | 364 | } |
| 361 | } | 365 | } |
| 362 | 366 | ||
| 363 | -watch(() => [props.bgUrl, props.qrUrl], () => { | 367 | +watch( |
| 368 | + () => [props.bgUrl, props.qrUrl], | ||
| 369 | + () => { | ||
| 364 | generatePoster() | 370 | generatePoster() |
| 365 | -}) | 371 | + } |
| 372 | +) | ||
| 366 | 373 | ||
| 367 | onMounted(() => { | 374 | onMounted(() => { |
| 368 | // 稍微延时确保字体等资源就绪(虽然canvas不强依赖DOM渲染,但字体加载是全局的) | 375 | // 稍微延时确保字体等资源就绪(虽然canvas不强依赖DOM渲染,但字体加载是全局的) |
| ... | @@ -370,7 +377,7 @@ onMounted(() => { | ... | @@ -370,7 +377,7 @@ onMounted(() => { |
| 370 | }) | 377 | }) |
| 371 | 378 | ||
| 372 | defineExpose({ | 379 | defineExpose({ |
| 373 | - generatePoster | 380 | + generatePoster, |
| 374 | }) | 381 | }) |
| 375 | </script> | 382 | </script> |
| 376 | 383 | ... | ... |
| ... | @@ -8,12 +8,12 @@ | ... | @@ -8,12 +8,12 @@ |
| 8 | <template> | 8 | <template> |
| 9 | <div class="welcome-content"> | 9 | <div class="welcome-content"> |
| 10 | <!-- 标题区域 --> | 10 | <!-- 标题区域 --> |
| 11 | - <div class="mt-20 flex flex-col items-center z-10 w-full px-8"> | 11 | + <div class="z-10 mt-20 flex w-full flex-col items-center px-8"> |
| 12 | - <img :src="titleImg" class="w-full max-w-[300px] mb-4 object-contain" alt="美乐爱觉" /> | 12 | + <img :src="titleImg" class="mb-4 w-full max-w-[300px] object-contain" alt="生命力教育联盟" /> |
| 13 | </div> | 13 | </div> |
| 14 | 14 | ||
| 15 | <!-- 功能入口区域 - 水平布局,自动推到底部 --> | 15 | <!-- 功能入口区域 - 水平布局,自动推到底部 --> |
| 16 | - <div class="mt-auto entry-orbit"> | 16 | + <div class="entry-orbit mt-auto"> |
| 17 | <div class="orbit-entries"> | 17 | <div class="orbit-entries"> |
| 18 | <WelcomeEntryItem | 18 | <WelcomeEntryItem |
| 19 | v-for="entry in entries" | 19 | v-for="entry in entries" |
| ... | @@ -39,7 +39,7 @@ const entries = ref(welcomeEntries) | ... | @@ -39,7 +39,7 @@ const entries = ref(welcomeEntries) |
| 39 | // 导入标题图片 | 39 | // 导入标题图片 |
| 40 | const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title007@2x.png' | 40 | const titleImg = 'https://cdn.ipadbiz.cn/mlaj/recall/img/title007@2x.png' |
| 41 | 41 | ||
| 42 | -const handleEntryClick = (entry) => { | 42 | +const handleEntryClick = entry => { |
| 43 | if (entry.isExternal) { | 43 | if (entry.isExternal) { |
| 44 | // 外部链接:获取用户ID并拼接 | 44 | // 外部链接:获取用户ID并拼接 |
| 45 | const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}') | 45 | const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}') |
| ... | @@ -103,7 +103,8 @@ const handleEntryClick = (entry) => { | ... | @@ -103,7 +103,8 @@ const handleEntryClick = (entry) => { |
| 103 | 103 | ||
| 104 | // 动画定义 | 104 | // 动画定义 |
| 105 | @keyframes float { | 105 | @keyframes float { |
| 106 | - 0%, 100% { | 106 | + 0%, |
| 107 | + 100% { | ||
| 107 | transform: translateY(0); | 108 | transform: translateY(0); |
| 108 | } | 109 | } |
| 109 | 50% { | 110 | 50% { | ... | ... |
| ... | @@ -5,7 +5,7 @@ | ... | @@ -5,7 +5,7 @@ |
| 5 | * @FilePath: /mlaj/src/composables/useShare.js | 5 | * @FilePath: /mlaj/src/composables/useShare.js |
| 6 | * @Description: 微信分享相关逻辑 | 6 | * @Description: 微信分享相关逻辑 |
| 7 | */ | 7 | */ |
| 8 | -import wx from 'weixin-js-sdk'; | 8 | +import wx from 'weixin-js-sdk' |
| 9 | 9 | ||
| 10 | /** | 10 | /** |
| 11 | * @function normalize_image_url | 11 | * @function normalize_image_url |
| ... | @@ -14,15 +14,15 @@ import wx from 'weixin-js-sdk'; | ... | @@ -14,15 +14,15 @@ import wx from 'weixin-js-sdk'; |
| 14 | * @returns {string} 处理后的图片地址 | 14 | * @returns {string} 处理后的图片地址 |
| 15 | */ | 15 | */ |
| 16 | function normalize_image_url(src) { | 16 | function normalize_image_url(src) { |
| 17 | - if (!src) return ''; | 17 | + if (!src) return '' |
| 18 | if (src.includes('cdn.ipadbiz.cn')) { | 18 | if (src.includes('cdn.ipadbiz.cn')) { |
| 19 | - const compress = 'imageMogr2/thumbnail/200x/strip/quality/70'; | 19 | + const compress = 'imageMogr2/thumbnail/200x/strip/quality/70' |
| 20 | if (src.includes('?')) { | 20 | if (src.includes('?')) { |
| 21 | - return src.includes(compress) ? src : `${src}&${compress}`; | 21 | + return src.includes(compress) ? src : `${src}&${compress}` |
| 22 | } | 22 | } |
| 23 | - return `${src}?${compress}`; | 23 | + return `${src}?${compress}` |
| 24 | } | 24 | } |
| 25 | - return src; | 25 | + return src |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | /** | 28 | /** |
| ... | @@ -41,30 +41,30 @@ function normalize_image_url(src) { | ... | @@ -41,30 +41,30 @@ function normalize_image_url(src) { |
| 41 | * @param {string} params.imgUrl 分享图标地址 | 41 | * @param {string} params.imgUrl 分享图标地址 |
| 42 | * @returns {void} | 42 | * @returns {void} |
| 43 | */ | 43 | */ |
| 44 | -export const sharePage = ({ title = '美乐爱觉', desc = '', imgUrl = '' }) => { | 44 | +export const sharePage = ({ title = '生命力教育联盟', desc = '', imgUrl = '' }) => { |
| 45 | const shareData = { | 45 | const shareData = { |
| 46 | title, // 分享标题 | 46 | title, // 分享标题 |
| 47 | desc, // 分享描述 | 47 | desc, // 分享描述 |
| 48 | link: location.origin + location.pathname + location.hash, // 分享链接,需与公众号 JS 安全域名一致 | 48 | link: location.origin + location.pathname + location.hash, // 分享链接,需与公众号 JS 安全域名一致 |
| 49 | imgUrl: normalize_image_url(imgUrl), // 分享图标,按规则追加压缩参数 | 49 | imgUrl: normalize_image_url(imgUrl), // 分享图标,按规则追加压缩参数 |
| 50 | - success: function () { | 50 | + success() { |
| 51 | // 设置成功回调 | 51 | // 设置成功回调 |
| 52 | - } | 52 | + }, |
| 53 | } | 53 | } |
| 54 | 54 | ||
| 55 | if (wx && typeof wx.ready === 'function') { | 55 | if (wx && typeof wx.ready === 'function') { |
| 56 | wx.ready(() => { | 56 | wx.ready(() => { |
| 57 | // 分享好友(微信好友或qq好友) | 57 | // 分享好友(微信好友或qq好友) |
| 58 | - wx.updateAppMessageShareData(shareData); | 58 | + wx.updateAppMessageShareData(shareData) |
| 59 | // 分享到朋友圈或qq空间 | 59 | // 分享到朋友圈或qq空间 |
| 60 | - wx.updateTimelineShareData(shareData); | 60 | + wx.updateTimelineShareData(shareData) |
| 61 | // 分享到腾讯微博 | 61 | // 分享到腾讯微博 |
| 62 | if (typeof wx.onMenuShareWeibo === 'function') { | 62 | if (typeof wx.onMenuShareWeibo === 'function') { |
| 63 | - wx.onMenuShareWeibo(shareData); | 63 | + wx.onMenuShareWeibo(shareData) |
| 64 | } | 64 | } |
| 65 | - }); | 65 | + }) |
| 66 | } else { | 66 | } else { |
| 67 | // 微信 JSSDK 未初始化或未就绪,分享配置可能不会生效 | 67 | // 微信 JSSDK 未初始化或未就绪,分享配置可能不会生效 |
| 68 | - console.warn('微信 JSSDK 未就绪:分享配置可能未生效'); | 68 | + console.warn('微信 JSSDK 未就绪:分享配置可能未生效') |
| 69 | } | 69 | } |
| 70 | } | 70 | } | ... | ... |
This diff is collapsed. Click to expand it.
| ... | @@ -23,7 +23,7 @@ export const routes = [ | ... | @@ -23,7 +23,7 @@ export const routes = [ |
| 23 | path: '/', | 23 | path: '/', |
| 24 | name: 'HomePage', | 24 | name: 'HomePage', |
| 25 | component: () => import('../views/HomePage.vue'), | 25 | component: () => import('../views/HomePage.vue'), |
| 26 | - meta: { title: '美乐爱觉' }, | 26 | + meta: { title: '生命力教育联盟' }, |
| 27 | }, | 27 | }, |
| 28 | { | 28 | { |
| 29 | path: '/courses', | 29 | path: '/courses', |
| ... | @@ -158,7 +158,7 @@ export const routes = [ | ... | @@ -158,7 +158,7 @@ export const routes = [ |
| 158 | path: '/recall/choose', | 158 | path: '/recall/choose', |
| 159 | name: 'ChoosePage', | 159 | name: 'ChoosePage', |
| 160 | component: () => import('../views/recall/ChoosePage.vue'), | 160 | component: () => import('../views/recall/ChoosePage.vue'), |
| 161 | - meta: { title: '美乐爱觉AI星球' }, | 161 | + meta: { title: '生命力教育联盟AI星球' }, |
| 162 | }, | 162 | }, |
| 163 | { | 163 | { |
| 164 | path: '/checkout', | 164 | path: '/checkout', | ... | ... |
This diff is collapsed. Click to expand it.
| 1 | <template> | 1 | <template> |
| 2 | - <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"> | 2 | + <div |
| 3 | + 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" | ||
| 4 | + > | ||
| 3 | <div class="sm:mx-auto sm:w-full sm:max-w-md"> | 5 | <div class="sm:mx-auto sm:w-full sm:max-w-md"> |
| 4 | - <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1> | 6 | + <h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1> |
| 5 | <h2 class="text-center text-xl font-medium text-gray-600">重置密码</h2> | 7 | <h2 class="text-center text-xl font-medium text-gray-600">重置密码</h2> |
| 6 | </div> | 8 | </div> |
| 7 | 9 | ||
| 8 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> | 10 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> |
| 9 | - <FrostedGlass class="py-8 px-6 rounded-lg"> | 11 | + <FrostedGlass class="rounded-lg px-6 py-8"> |
| 10 | - <div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md"> | 12 | + <div |
| 13 | + v-if="error" | ||
| 14 | + class="mb-4 rounded-md border border-red-400 bg-red-100 px-4 py-3 text-red-700" | ||
| 15 | + > | ||
| 11 | {{ error }} | 16 | {{ error }} |
| 12 | </div> | 17 | </div> |
| 13 | 18 | ||
| ... | @@ -25,7 +30,7 @@ | ... | @@ -25,7 +30,7 @@ |
| 25 | maxlength="11" | 30 | maxlength="11" |
| 26 | @input="formData.phone = formData.phone.replace(/\D/g, '')" | 31 | @input="formData.phone = formData.phone.replace(/\D/g, '')" |
| 27 | @blur="validatePhone" | 32 | @blur="validatePhone" |
| 28 | - 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" | 33 | + 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" |
| 29 | /> | 34 | /> |
| 30 | </div> | 35 | </div> |
| 31 | 36 | ||
| ... | @@ -40,13 +45,13 @@ | ... | @@ -40,13 +45,13 @@ |
| 40 | type="text" | 45 | type="text" |
| 41 | required | 46 | required |
| 42 | maxlength="6" | 47 | maxlength="6" |
| 43 | - 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" | 48 | + 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" |
| 44 | /> | 49 | /> |
| 45 | <button | 50 | <button |
| 46 | type="button" | 51 | type="button" |
| 47 | :disabled="countdown > 0 || !isPhoneValid" | 52 | :disabled="countdown > 0 || !isPhoneValid" |
| 48 | @click="sendVerificationCode" | 53 | @click="sendVerificationCode" |
| 49 | - 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" | 54 | + 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" |
| 50 | > | 55 | > |
| 51 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} | 56 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} |
| 52 | </button> | 57 | </button> |
| ... | @@ -63,7 +68,7 @@ | ... | @@ -63,7 +68,7 @@ |
| 63 | type="password" | 68 | type="password" |
| 64 | required | 69 | required |
| 65 | minlength="6" | 70 | minlength="6" |
| 66 | - 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" | 71 | + 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" |
| 67 | /> | 72 | /> |
| 68 | </div> | 73 | </div> |
| 69 | 74 | ||
| ... | @@ -77,7 +82,7 @@ | ... | @@ -77,7 +82,7 @@ |
| 77 | type="password" | 82 | type="password" |
| 78 | required | 83 | required |
| 79 | minlength="6" | 84 | minlength="6" |
| 80 | - 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" | 85 | + 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" |
| 81 | /> | 86 | /> |
| 82 | </div> | 87 | </div> |
| 83 | 88 | ||
| ... | @@ -85,15 +90,15 @@ | ... | @@ -85,15 +90,15 @@ |
| 85 | <button | 90 | <button |
| 86 | type="submit" | 91 | type="submit" |
| 87 | :disabled="loading" | 92 | :disabled="loading" |
| 88 | - 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" | 93 | + 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" |
| 89 | - :class="{ 'opacity-70 cursor-not-allowed': loading }" | 94 | + :class="{ 'cursor-not-allowed opacity-70': loading }" |
| 90 | > | 95 | > |
| 91 | {{ loading ? '提交中...' : '重置密码' }} | 96 | {{ loading ? '提交中...' : '重置密码' }} |
| 92 | </button> | 97 | </button> |
| 93 | </div> | 98 | </div> |
| 94 | </form> | 99 | </form> |
| 95 | 100 | ||
| 96 | - <div class="text-center mt-6"> | 101 | + <div class="mt-6 text-center"> |
| 97 | <p class="text-sm text-gray-600"> | 102 | <p class="text-sm text-gray-600"> |
| 98 | 记起密码了? | 103 | 记起密码了? |
| 99 | <router-link to="/login" class="font-medium text-green-600 hover:text-green-500"> | 104 | <router-link to="/login" class="font-medium text-green-600 hover:text-green-500"> |
| ... | @@ -110,9 +115,9 @@ | ... | @@ -110,9 +115,9 @@ |
| 110 | import { ref, reactive } from 'vue' | 115 | import { ref, reactive } from 'vue' |
| 111 | import { useRouter } from 'vue-router' | 116 | import { useRouter } from 'vue-router' |
| 112 | import FrostedGlass from '@/components/effects/FrostedGlass.vue' | 117 | import FrostedGlass from '@/components/effects/FrostedGlass.vue' |
| 113 | -import { smsAPI } from '@/api/common'; | 118 | +import { smsAPI } from '@/api/common' |
| 114 | -import { resetPasswordAPI } from '@/api/users'; | 119 | +import { resetPasswordAPI } from '@/api/users' |
| 115 | -import { showToast } from 'vant'; | 120 | +import { showToast } from 'vant' |
| 116 | 121 | ||
| 117 | const router = useRouter() | 122 | const router = useRouter() |
| 118 | const error = ref('') | 123 | const error = ref('') |
| ... | @@ -124,7 +129,7 @@ const formData = reactive({ | ... | @@ -124,7 +129,7 @@ const formData = reactive({ |
| 124 | phone: '', | 129 | phone: '', |
| 125 | verificationCode: '', | 130 | verificationCode: '', |
| 126 | password: '', | 131 | password: '', |
| 127 | - confirmPassword: '' | 132 | + confirmPassword: '', |
| 128 | }) | 133 | }) |
| 129 | 134 | ||
| 130 | const startCountdown = () => { | 135 | const startCountdown = () => { |
| ... | @@ -187,7 +192,7 @@ const handleSubmit = async () => { | ... | @@ -187,7 +192,7 @@ const handleSubmit = async () => { |
| 187 | const { code } = await resetPasswordAPI({ | 192 | const { code } = await resetPasswordAPI({ |
| 188 | mobile: formData.phone, | 193 | mobile: formData.phone, |
| 189 | sms_code: formData.verificationCode, | 194 | sms_code: formData.verificationCode, |
| 190 | - password: formData.password | 195 | + password: formData.password, |
| 191 | }) | 196 | }) |
| 192 | 197 | ||
| 193 | if (code === 1) { | 198 | if (code === 1) { |
| ... | @@ -195,7 +200,6 @@ const handleSubmit = async () => { | ... | @@ -195,7 +200,6 @@ const handleSubmit = async () => { |
| 195 | // 重置成功后跳转到登录页 | 200 | // 重置成功后跳转到登录页 |
| 196 | router.push('/login') | 201 | router.push('/login') |
| 197 | } | 202 | } |
| 198 | - | ||
| 199 | } catch (err) { | 203 | } catch (err) { |
| 200 | console.error('Reset password error:', err) | 204 | console.error('Reset password error:', err) |
| 201 | error.value = '重置密码失败,请稍后重试' | 205 | error.value = '重置密码失败,请稍后重试' | ... | ... |
| ... | @@ -8,7 +8,7 @@ | ... | @@ -8,7 +8,7 @@ |
| 8 | size="10rem" | 8 | size="10rem" |
| 9 | style="margin-bottom: 0.5rem" | 9 | style="margin-bottom: 0.5rem" |
| 10 | /> | 10 | /> |
| 11 | - <h1 class="mb-2 text-center text-3xl font-bold text-gray-800">美乐爱觉教育</h1> | 11 | + <h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1> |
| 12 | <!-- <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2> --> | 12 | <!-- <h2 class="text-center text-xl font-medium text-gray-600">欢迎回来</h2> --> |
| 13 | </div> | 13 | </div> |
| 14 | 14 | ||
| ... | @@ -168,7 +168,7 @@ | ... | @@ -168,7 +168,7 @@ |
| 168 | class="cursor-pointer font-medium text-green-600 hover:text-green-500" | 168 | class="cursor-pointer font-medium text-green-600 hover:text-green-500" |
| 169 | @click="userAgreementRef.openAgreement()" | 169 | @click="userAgreementRef.openAgreement()" |
| 170 | > | 170 | > |
| 171 | - 《美乐爱觉宇宙用户协议》 | 171 | + 《生命力教育联盟宇宙用户协议》 |
| 172 | </span> | 172 | </span> |
| 173 | <UserAgreement ref="userAgreementRef" /> | 173 | <UserAgreement ref="userAgreementRef" /> |
| 174 | </p> | 174 | </p> | ... | ... |
| 1 | <template> | 1 | <template> |
| 2 | - <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"> | 2 | + <div |
| 3 | + 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" | ||
| 4 | + > | ||
| 3 | <div class="sm:mx-auto sm:w-full sm:max-w-md"> | 5 | <div class="sm:mx-auto sm:w-full sm:max-w-md"> |
| 4 | - <h1 class="text-center text-3xl font-bold text-gray-800 mb-2">美乐爱觉教育</h1> | 6 | + <h1 class="mb-2 text-center text-3xl font-bold text-gray-800">生命力教育联盟教育</h1> |
| 5 | <h2 class="text-center text-xl font-medium text-gray-600">创建账号</h2> | 7 | <h2 class="text-center text-xl font-medium text-gray-600">创建账号</h2> |
| 6 | </div> | 8 | </div> |
| 7 | 9 | ||
| 8 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> | 10 | <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> |
| 9 | - <FrostedGlass class="py-8 px-6 rounded-lg"> | 11 | + <FrostedGlass class="rounded-lg px-6 py-8"> |
| 10 | - <div v-if="error" class="mb-4 bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-md"> | 12 | + <div |
| 13 | + v-if="error" | ||
| 14 | + class="mb-4 rounded-md border border-red-400 bg-red-100 px-4 py-3 text-red-700" | ||
| 15 | + > | ||
| 11 | {{ error }} | 16 | {{ error }} |
| 12 | </div> | 17 | </div> |
| 13 | 18 | ||
| ... | @@ -21,7 +26,7 @@ | ... | @@ -21,7 +26,7 @@ |
| 21 | v-model="formData.name" | 26 | v-model="formData.name" |
| 22 | type="text" | 27 | type="text" |
| 23 | required | 28 | required |
| 24 | - 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" | 29 | + 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" |
| 25 | /> | 30 | /> |
| 26 | </div> | 31 | </div> |
| 27 | 32 | ||
| ... | @@ -39,7 +44,7 @@ | ... | @@ -39,7 +44,7 @@ |
| 39 | maxlength="11" | 44 | maxlength="11" |
| 40 | @input="formData.phone = formData.phone.replace(/\D/g, '')" | 45 | @input="formData.phone = formData.phone.replace(/\D/g, '')" |
| 41 | @blur="validatePhone" | 46 | @blur="validatePhone" |
| 42 | - 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" | 47 | + 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" |
| 43 | /> | 48 | /> |
| 44 | </div> | 49 | </div> |
| 45 | 50 | ||
| ... | @@ -54,13 +59,13 @@ | ... | @@ -54,13 +59,13 @@ |
| 54 | type="text" | 59 | type="text" |
| 55 | required | 60 | required |
| 56 | maxlength="6" | 61 | maxlength="6" |
| 57 | - 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" | 62 | + 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" |
| 58 | /> | 63 | /> |
| 59 | <button | 64 | <button |
| 60 | type="button" | 65 | type="button" |
| 61 | :disabled="countdown > 0 || !isPhoneValid" | 66 | :disabled="countdown > 0 || !isPhoneValid" |
| 62 | @click="sendVerificationCode" | 67 | @click="sendVerificationCode" |
| 63 | - 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" | 68 | + 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" |
| 64 | > | 69 | > |
| 65 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} | 70 | {{ countdown > 0 ? `${countdown}秒后重试` : '获取验证码' }} |
| 66 | </button> | 71 | </button> |
| ... | @@ -78,7 +83,7 @@ | ... | @@ -78,7 +83,7 @@ |
| 78 | autocomplete="new-password" | 83 | autocomplete="new-password" |
| 79 | required | 84 | required |
| 80 | minlength="6" | 85 | minlength="6" |
| 81 | - 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" | 86 | + 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" |
| 82 | /> | 87 | /> |
| 83 | </div> | 88 | </div> |
| 84 | 89 | ||
| ... | @@ -93,7 +98,7 @@ | ... | @@ -93,7 +98,7 @@ |
| 93 | autocomplete="new-password" | 98 | autocomplete="new-password" |
| 94 | required | 99 | required |
| 95 | minlength="6" | 100 | minlength="6" |
| 96 | - 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" | 101 | + 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" |
| 97 | /> | 102 | /> |
| 98 | </div> | 103 | </div> |
| 99 | 104 | ||
| ... | @@ -105,15 +110,25 @@ | ... | @@ -105,15 +110,25 @@ |
| 105 | checked-color="#4caf50" | 110 | checked-color="#4caf50" |
| 106 | class="scale-90" | 111 | class="scale-90" |
| 107 | icon-size="18px" | 112 | icon-size="18px" |
| 108 | - ><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> | 113 | + ><span class="text-sm" |
| 114 | + >我已阅读并同意 | ||
| 115 | + <a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openTerms" | ||
| 116 | + >用户协议</a | ||
| 117 | + > | ||
| 118 | + 和 | ||
| 119 | + <a href="#" class="text-green-600 hover:text-green-500" @click.prevent="openPrivacy" | ||
| 120 | + >隐私政策</a | ||
| 121 | + ></span | ||
| 122 | + ></van-checkbox | ||
| 123 | + > | ||
| 109 | </div> | 124 | </div> |
| 110 | 125 | ||
| 111 | <div> | 126 | <div> |
| 112 | <button | 127 | <button |
| 113 | type="submit" | 128 | type="submit" |
| 114 | :disabled="loading" | 129 | :disabled="loading" |
| 115 | - 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" | 130 | + 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" |
| 116 | - :class="{ 'opacity-70 cursor-not-allowed': loading }" | 131 | + :class="{ 'cursor-not-allowed opacity-70': loading }" |
| 117 | > | 132 | > |
| 118 | {{ loading ? '注册中...' : '立即注册' }} | 133 | {{ loading ? '注册中...' : '立即注册' }} |
| 119 | </button> | 134 | </button> |
| ... | @@ -154,7 +169,7 @@ | ... | @@ -154,7 +169,7 @@ |
| 154 | </div> | 169 | </div> |
| 155 | </div> --> | 170 | </div> --> |
| 156 | 171 | ||
| 157 | - <div class="text-center mt-6"> | 172 | + <div class="mt-6 text-center"> |
| 158 | <p class="text-sm text-gray-600"> | 173 | <p class="text-sm text-gray-600"> |
| 159 | 已有账号? | 174 | 已有账号? |
| 160 | <router-link to="/login" class="font-medium text-green-600 hover:text-green-500"> | 175 | <router-link to="/login" class="font-medium text-green-600 hover:text-green-500"> |
| ... | @@ -165,16 +180,8 @@ | ... | @@ -165,16 +180,8 @@ |
| 165 | </FrostedGlass> | 180 | </FrostedGlass> |
| 166 | </div> | 181 | </div> |
| 167 | 182 | ||
| 168 | - <TermsPopup | 183 | + <TermsPopup v-model:show="showTerms" :type="popupType" :title="popupTitle" /> |
| 169 | - v-model:show="showTerms" | 184 | + <TermsPopup v-model:show="showPrivacy" :type="popupType" :title="popupTitle" /> |
| 170 | - :type="popupType" | ||
| 171 | - :title="popupTitle" | ||
| 172 | - /> | ||
| 173 | - <TermsPopup | ||
| 174 | - v-model:show="showPrivacy" | ||
| 175 | - :type="popupType" | ||
| 176 | - :title="popupTitle" | ||
| 177 | - /> | ||
| 178 | </div> | 185 | </div> |
| 179 | </template> | 186 | </template> |
| 180 | 187 | ||
| ... | @@ -184,13 +191,13 @@ import { useRoute, useRouter } from 'vue-router' | ... | @@ -184,13 +191,13 @@ import { useRoute, useRouter } from 'vue-router' |
| 184 | import FrostedGlass from '@/components/effects/FrostedGlass.vue' | 191 | import FrostedGlass from '@/components/effects/FrostedGlass.vue' |
| 185 | import TermsPopup from '@/components/common/TermsPopup.vue' | 192 | import TermsPopup from '@/components/common/TermsPopup.vue' |
| 186 | import { useAuth } from '@/contexts/auth' | 193 | import { useAuth } from '@/contexts/auth' |
| 187 | -import { useTitle } from '@vueuse/core'; | 194 | +import { useTitle } from '@vueuse/core' |
| 188 | -import { smsAPI } from '@/api/common'; | 195 | +import { smsAPI } from '@/api/common' |
| 189 | -import { registerAPI } from '@/api/users'; | 196 | +import { registerAPI } from '@/api/users' |
| 190 | -import { showToast } from 'vant'; | 197 | +import { showToast } from 'vant' |
| 191 | 198 | ||
| 192 | -const $route = useRoute(); | 199 | +const $route = useRoute() |
| 193 | -useTitle($route.meta.title); | 200 | +useTitle($route.meta.title) |
| 194 | 201 | ||
| 195 | const router = useRouter() | 202 | const router = useRouter() |
| 196 | const { login } = useAuth() | 203 | const { login } = useAuth() |
| ... | @@ -203,7 +210,7 @@ const formData = reactive({ | ... | @@ -203,7 +210,7 @@ const formData = reactive({ |
| 203 | verificationCode: '', | 210 | verificationCode: '', |
| 204 | password: '', | 211 | password: '', |
| 205 | confirmPassword: '', | 212 | confirmPassword: '', |
| 206 | - agreeTerms: false | 213 | + agreeTerms: false, |
| 207 | }) | 214 | }) |
| 208 | 215 | ||
| 209 | const startCountdown = () => { | 216 | const startCountdown = () => { |
| ... | @@ -293,7 +300,7 @@ const handleSubmit = async () => { | ... | @@ -293,7 +300,7 @@ const handleSubmit = async () => { |
| 293 | const success = login({ | 300 | const success = login({ |
| 294 | name: formData.name, | 301 | name: formData.name, |
| 295 | mobile: formData.phone, | 302 | mobile: formData.phone, |
| 296 | - avatar: '' | 303 | + avatar: '', |
| 297 | }) | 304 | }) |
| 298 | if (success) { | 305 | if (success) { |
| 299 | router.push('/') | 306 | router.push('/') | ... | ... |
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
-
Please register or login to post a comment