feat(课程详情页): 添加咨询弹窗功能
实现课程详情页咨询弹窗功能,包含以下特性: - 底部弹出层设计,仅显示关闭按钮 - 支持富文本内容展示和点击复制 - 电话咨询功能,点击直接拨打 - 自动压缩富文本中的图片
Showing
2 changed files
with
159 additions
and
16 deletions
| ... | @@ -31,6 +31,14 @@ https://oa-dev.onwall.cn/f/mlaj | ... | @@ -31,6 +31,14 @@ https://oa-dev.onwall.cn/f/mlaj |
| 31 | - 位置:`/src/views/courses/CourseDetailPage.vue`,在 `onMounted` 插入,`onUnmounted` 清理。 | 31 | - 位置:`/src/views/courses/CourseDetailPage.vue`,在 `onMounted` 插入,`onUnmounted` 清理。 |
| 32 | - 函数:`build_og_image_url(src)`、`set_og_meta(payload)`、`remove_og_meta()`。 | 32 | - 函数:`build_og_image_url(src)`、`set_og_meta(payload)`、`remove_og_meta()`。 |
| 33 | 33 | ||
| 34 | + - 课程详情页咨询弹窗(Mock) | ||
| 35 | + - 入口:详情页顶部快捷操作中的“咨询”按钮。 | ||
| 36 | + - 展示:底部弹出层,仅底部关闭按钮;内容支持富文本展示。 | ||
| 37 | + - 电话:显示咨询电话;点击即可直接拨打(`tel:`)。 | ||
| 38 | + - 咨询信息:富文本区域点击即可复制到剪切板;复制成功后提示。 | ||
| 39 | + - 图片压缩:富文本中若包含 `cdn.ipadbiz.cn` 图片,使用 `?imageMogr2/thumbnail/200x/strip/quality/70` 参数。 | ||
| 40 | + - 位置:`/src/views/courses/CourseDetailPage.vue`,“咨询弹窗”模板与交互逻辑(`open_consult_dialog`、`close_consult_dialog`、`call_phone`、`copy_consult_info`)。 | ||
| 41 | + | ||
| 34 | - 401拦截策略优化(公开页面不再跳登录) | 42 | - 401拦截策略优化(公开页面不再跳登录) |
| 35 | - 行为:接口返回 `code=401` 时,不再对公开页面(如课程详情 `/courses/:id`)进行登录重定向;仅当当前路由确实需要登录权限时才跳转至登录页。 | 43 | - 行为:接口返回 `code=401` 时,不再对公开页面(如课程详情 `/courses/:id`)进行登录重定向;仅当当前路由确实需要登录权限时才跳转至登录页。 |
| 36 | - 原理:响应拦截器调用路由守卫 `checkAuth` 判断当前路由是否为受限页面,受限则清理登录信息并附带 `redirect` 重定向至登录页;公开页面保持当前页,由业务自行处理401。 | 44 | - 原理:响应拦截器调用路由守卫 `checkAuth` 判断当前路由是否为受限页面,受限则清理登录信息并附带 `redirect` 重定向至登录页;公开页面保持当前页,由业务自行处理401。 | ... | ... |
| ... | @@ -209,12 +209,21 @@ | ... | @@ -209,12 +209,21 @@ |
| 209 | stroke-linecap="round" | 209 | stroke-linecap="round" |
| 210 | stroke-linejoin="round" | 210 | stroke-linejoin="round" |
| 211 | stroke-width="2" | 211 | stroke-width="2" |
| 212 | - d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" | 212 | + d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" |
| 213 | /> | 213 | /> |
| 214 | </svg> | 214 | </svg> |
| 215 | - 咨询 | 215 | + 分享 |
| 216 | </button> --> | 216 | </button> --> |
| 217 | - <!-- <button class="flex flex-col items-center text-gray-500 text-xs"> | 217 | + <button class="flex flex-col items-center text-gray-500 text-xs transition-transform duration-300" |
| 218 | + @click="toggleFavorite" :class="{ 'animate-favorite': isFavorite }"> | ||
| 219 | + <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform duration-300" | ||
| 220 | + :fill="isFavorite ? 'red' : 'none'" viewBox="0 0 24 24" :stroke="isFavorite ? 'red' : 'currentColor'"> | ||
| 221 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||
| 222 | + d="M4.318 6.318 a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682 a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318 a4.5 4.5 0 00-6.364 0z" /> | ||
| 223 | + </svg> | ||
| 224 | + 收藏 | ||
| 225 | + </button> | ||
| 226 | + <button class="flex flex-col items-center text-gray-500 text-xs" @click="open_consult_dialog"> | ||
| 218 | <svg | 227 | <svg |
| 219 | xmlns="http://www.w3.org/2000/svg" | 228 | xmlns="http://www.w3.org/2000/svg" |
| 220 | class="h-6 w-6" | 229 | class="h-6 w-6" |
| ... | @@ -226,19 +235,10 @@ | ... | @@ -226,19 +235,10 @@ |
| 226 | stroke-linecap="round" | 235 | stroke-linecap="round" |
| 227 | stroke-linejoin="round" | 236 | stroke-linejoin="round" |
| 228 | stroke-width="2" | 237 | stroke-width="2" |
| 229 | - d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" | 238 | + d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" |
| 230 | /> | 239 | /> |
| 231 | </svg> | 240 | </svg> |
| 232 | - 分享 | 241 | + 咨询 |
| 233 | - </button> --> | ||
| 234 | - <button class="flex flex-col items-center text-gray-500 text-xs transition-transform duration-300" | ||
| 235 | - @click="toggleFavorite" :class="{ 'animate-favorite': isFavorite }"> | ||
| 236 | - <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 transition-transform duration-300" | ||
| 237 | - :fill="isFavorite ? 'red' : 'none'" viewBox="0 0 24 24" :stroke="isFavorite ? 'red' : 'currentColor'"> | ||
| 238 | - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||
| 239 | - d="M4.318 6.318 a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682 a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318 a4.5 4.5 0 00-6.364 0z" /> | ||
| 240 | - </svg> | ||
| 241 | - 收藏 | ||
| 242 | </button> | 242 | </button> |
| 243 | </div> | 243 | </div> |
| 244 | <div class="flex items-center"> | 244 | <div class="flex items-center"> |
| ... | @@ -327,6 +327,46 @@ | ... | @@ -327,6 +327,46 @@ |
| 327 | </template> | 327 | </template> |
| 328 | </div> | 328 | </div> |
| 329 | </van-popup> | 329 | </van-popup> |
| 330 | + | ||
| 331 | + <!-- 咨询弹窗:底部只有关闭按钮,内容支持富文本 --> | ||
| 332 | + <van-popup | ||
| 333 | + v-model:show="show_consult_dialog" | ||
| 334 | + round | ||
| 335 | + position="bottom" | ||
| 336 | + :style="{ minHeight: '30%', maxHeight: '80%', width: '100%' }" | ||
| 337 | + > | ||
| 338 | + <div class="ConsultPopup p-4"> | ||
| 339 | + <!-- 标题与关闭图标 --> | ||
| 340 | + <div class="flex justify-between items-center mb-3"> | ||
| 341 | + <h3 class="font-medium">咨询信息</h3> | ||
| 342 | + <van-icon name="cross" @click="close_consult_dialog" /> | ||
| 343 | + </div> | ||
| 344 | + | ||
| 345 | + <!-- 电话信息:点击直接拨打 --> | ||
| 346 | + <div class="bg-gray-50 border border-gray-200 rounded-lg p-3 mb-4"> | ||
| 347 | + <div class="flex items-center justify-between"> | ||
| 348 | + <div class="flex items-center"> | ||
| 349 | + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-green-500 mr-2" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | ||
| 350 | + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 5a2 2 0 012-2h2.28a2 2 0 011.789 1.106l1.152 2.305a2 2 0 01-.42 2.317L9.384 10.09a16.001 16.001 0 006.526 6.526l1.356-1.102a2 2 0 012.317-.42l2.305 1.152A2 2 0 0121 18.72V21a2 2 0 01-2 2h-1a18 18 0 01-17-17V5z" /> | ||
| 351 | + </svg> | ||
| 352 | + <span class="text-gray-700">联系电话</span> | ||
| 353 | + </div> | ||
| 354 | + <a class="text-green-600 font-medium" :href="`tel:${consult_phone}`" @click.prevent="call_phone">{{ consult_phone }}</a> | ||
| 355 | + </div> | ||
| 356 | + </div> | ||
| 357 | + | ||
| 358 | + <!-- 富文本咨询信息:点击复制到剪切板 --> | ||
| 359 | + <div class="bg-white border border-gray-100 rounded-lg p-3"> | ||
| 360 | + <div class="text-gray-700 text-sm leading-6" v-html="consult_html" @click="copy_consult_info"></div> | ||
| 361 | + <div class="text-xs text-gray-400 mt-2">提示:点击上方任意文字即可复制咨询内容</div> | ||
| 362 | + </div> | ||
| 363 | + | ||
| 364 | + <!-- 底部关闭按钮(唯一操作) --> | ||
| 365 | + <div class="mt-4"> | ||
| 366 | + <button class="w-full bg-gradient-to-r from-green-500 to-green-600 text-white py-2 rounded-lg" @click="close_consult_dialog">关闭</button> | ||
| 367 | + </div> | ||
| 368 | + </div> | ||
| 369 | + </van-popup> | ||
| 330 | <van-back-top right="5vw" bottom="25vh" offset="600" /> | 370 | <van-back-top right="5vw" bottom="25vh" offset="600" /> |
| 331 | </AppLayout> | 371 | </AppLayout> |
| 332 | </template> | 372 | </template> |
| ... | @@ -494,6 +534,94 @@ const selectedCheckIn = ref(null) | ... | @@ -494,6 +534,94 @@ const selectedCheckIn = ref(null) |
| 494 | const isCheckingIn = ref(false) | 534 | const isCheckingIn = ref(false) |
| 495 | const checkInSuccess = ref(false) | 535 | const checkInSuccess = ref(false) |
| 496 | 536 | ||
| 537 | +// 咨询弹窗相关状态 | ||
| 538 | +/** | ||
| 539 | + * 展示咨询弹窗的显隐状态 | ||
| 540 | + * @type {import('vue').Ref<boolean>} | ||
| 541 | + */ | ||
| 542 | +const show_consult_dialog = ref(false) | ||
| 543 | + | ||
| 544 | +/** | ||
| 545 | + * 咨询联系电话(Mock 数据) | ||
| 546 | + * @type {import('vue').Ref<string>} | ||
| 547 | + */ | ||
| 548 | +const consult_phone = ref('400-888-8888') | ||
| 549 | + | ||
| 550 | +/** | ||
| 551 | + * 咨询富文本内容(Mock 数据) | ||
| 552 | + * 说明:示例中包含来自 cdn.ipadbiz.cn 的图片,带有压缩参数 | ||
| 553 | + * @type {import('vue').Ref<string>} | ||
| 554 | + */ | ||
| 555 | +const consult_html = ref( | ||
| 556 | + '<p><strong>课程咨询说明:</strong>如需了解课程安排、报名流程、发票开具等信息,请联系课程顾问。</p>' + | ||
| 557 | + '<p>可通过电话或复制下方咨询信息进行沟通。</p>' + | ||
| 558 | + '<p><img src="https://cdn.ipadbiz.cn/images/consult_demo.png?imageMogr2/thumbnail/200x/strip/quality/70" alt="咨询示例" style="max-width:100%;border-radius:8px;"/></p>' | ||
| 559 | +) | ||
| 560 | + | ||
| 561 | +/** | ||
| 562 | + * 打开咨询弹窗 | ||
| 563 | + * @returns {void} | ||
| 564 | + */ | ||
| 565 | +const open_consult_dialog = () => { | ||
| 566 | + show_consult_dialog.value = true | ||
| 567 | +} | ||
| 568 | + | ||
| 569 | +/** | ||
| 570 | + * 关闭咨询弹窗 | ||
| 571 | + * @returns {void} | ||
| 572 | + */ | ||
| 573 | +const close_consult_dialog = () => { | ||
| 574 | + show_consult_dialog.value = false | ||
| 575 | +} | ||
| 576 | + | ||
| 577 | +/** | ||
| 578 | + * 直接拨打咨询电话 | ||
| 579 | + * @returns {void} | ||
| 580 | + */ | ||
| 581 | +const call_phone = () => { | ||
| 582 | + const phone = consult_phone.value || '' | ||
| 583 | + if (phone) { | ||
| 584 | + window.location.href = `tel:${phone}` | ||
| 585 | + } | ||
| 586 | +} | ||
| 587 | + | ||
| 588 | +/** | ||
| 589 | + * 将富文本内容转换为纯文本 | ||
| 590 | + * @param {string} html 原始富文本 HTML 字符串 | ||
| 591 | + * @returns {string} 纯文本内容 | ||
| 592 | + */ | ||
| 593 | +const strip_html = (html) => { | ||
| 594 | + const text = (html || '').replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim() | ||
| 595 | + return text | ||
| 596 | +} | ||
| 597 | + | ||
| 598 | +/** | ||
| 599 | + * 复制咨询富文本信息为纯文本 | ||
| 600 | + * @returns {Promise<void>} | ||
| 601 | + */ | ||
| 602 | +const copy_consult_info = async () => { | ||
| 603 | + const text = strip_html(consult_html.value) | ||
| 604 | + try { | ||
| 605 | + if (navigator.clipboard && navigator.clipboard.writeText) { | ||
| 606 | + await navigator.clipboard.writeText(text) | ||
| 607 | + } else { | ||
| 608 | + const textarea = document.createElement('textarea') | ||
| 609 | + textarea.value = text | ||
| 610 | + textarea.style.position = 'fixed' | ||
| 611 | + textarea.style.top = '-1000px' | ||
| 612 | + document.body.appendChild(textarea) | ||
| 613 | + textarea.focus() | ||
| 614 | + textarea.select() | ||
| 615 | + document.execCommand('copy') | ||
| 616 | + document.body.removeChild(textarea) | ||
| 617 | + } | ||
| 618 | + showToast('咨询信息已复制') | ||
| 619 | + } catch (err) { | ||
| 620 | + console.error('复制失败: ', err) | ||
| 621 | + showToast('复制失败,请稍后重试') | ||
| 622 | + } | ||
| 623 | +} | ||
| 624 | + | ||
| 497 | const { addToCart, proceedToCheckout } = useCart() | 625 | const { addToCart, proceedToCheckout } = useCart() |
| 498 | 626 | ||
| 499 | 627 | ||
| ... | @@ -887,9 +1015,16 @@ setTimeout(() => { | ... | @@ -887,9 +1015,16 @@ setTimeout(() => { |
| 887 | </script> | 1015 | </script> |
| 888 | 1016 | ||
| 889 | <style lang="less"> | 1017 | <style lang="less"> |
| 890 | -.animate-favorite { | 1018 | +.ConsultPopup { |
| 891 | - animation: favorite-animation 0.5s ease; | 1019 | + // 咨询弹窗样式容器(使用 less 层级嵌套) |
| 1020 | + h3 { | ||
| 1021 | + // 标题样式 | ||
| 1022 | + font-weight: 500; | ||
| 1023 | + } | ||
| 892 | } | 1024 | } |
| 1025 | + .animate-favorite { | ||
| 1026 | + animation: favorite-animation 0.5s ease; | ||
| 1027 | + } | ||
| 893 | 1028 | ||
| 894 | @keyframes favorite-animation { | 1029 | @keyframes favorite-animation { |
| 895 | 0% { | 1030 | 0% { | ... | ... |
-
Please register or login to post a comment