feat(kefu): 个人中心导航栏与全局客服入口支持直达微信小程序原生客服会话
这个提交包含在:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 测试用例:验证微信小程序客服跳转逻辑及组件绑定
|
||||
* 覆盖范围:
|
||||
* 1. s-custom-navbar / navbar-item.vue 微信小程序 open-type="contact" 条件编译及客服判定逻辑
|
||||
* 2. detail-tabbar.vue 微信小程序客服按钮与卡片信息透传
|
||||
* 3. s-menu-tools.vue 全局快捷菜单客服按钮 open-type="contact" 适配
|
||||
* 4. feedback.vue 意见反馈页面客服按钮 open-type="contact" 适配
|
||||
* 5. aftersale/apply.vue & detail.vue 售后客服按钮适配
|
||||
* 6. chat/index.vue 微信小程序原生客服提示与快速接入组件
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const assert = require('assert');
|
||||
|
||||
console.log('=== 开始执行微信小程序客服跳转自动化测试 ===\n');
|
||||
|
||||
// 1. 验证 navbar-item.vue
|
||||
const navbarItemPath = path.resolve(__dirname, '../uniapp/sheep/components/s-custom-navbar/components/navbar-item.vue');
|
||||
assert(fs.existsSync(navbarItemPath), 'navbar-item.vue 文件必须存在');
|
||||
const navbarItemContent = fs.readFileSync(navbarItemPath, 'utf-8');
|
||||
|
||||
assert(navbarItemContent.includes('#ifdef MP-WEIXIN'), 'navbar-item.vue 必须包含微信小程序条件编译');
|
||||
assert(navbarItemContent.includes('open-type="contact"'), 'navbar-item.vue 必须包含 open-type="contact" 属性');
|
||||
assert(navbarItemContent.includes('isCustomerService'), 'navbar-item.vue 必须包含 isCustomerService 计算属性');
|
||||
assert(navbarItemContent.includes("url === '/pages/chat/index'"), 'navbar-item.vue 必须精准识别 /pages/chat/index 路由');
|
||||
assert(navbarItemContent.includes('c47b048175b325b1e78f837a3b696794'), 'navbar-item.vue 必须兜底兼容生产数据库客服图片MD5特征');
|
||||
assert(navbarItemContent.includes('#ifndef MP-WEIXIN'), 'navbar-item.vue 必须保持非小程序环境原有路由兼容性');
|
||||
console.log('✅ 测试点 1 通过: 顶部导航栏 navbar-item.vue 微信小程序客服按钮及条件编译正确');
|
||||
|
||||
// 2. 模拟 isCustomerService 逻辑验证
|
||||
function testCustomerServiceMatcher(item) {
|
||||
const url = item?.url || '';
|
||||
const src = item?.src || '';
|
||||
return (
|
||||
url === '/pages/chat/index' ||
|
||||
url.startsWith('/pages/chat') ||
|
||||
url.includes('chat') ||
|
||||
url.includes('kefu') ||
|
||||
url.includes('customer_service') ||
|
||||
src.includes('c47b048175b325b1e78f837a3b696794')
|
||||
);
|
||||
}
|
||||
|
||||
// 正常场景
|
||||
assert.strictEqual(
|
||||
testCustomerServiceMatcher({
|
||||
type: 'image',
|
||||
url: '/pages/chat/index',
|
||||
src: '/storage/decorate/20221115/c47b048175b325b1e78f837a3b696794.png'
|
||||
}),
|
||||
true,
|
||||
'生产环境会员中心导航栏客服项必须识别为客服'
|
||||
);
|
||||
|
||||
// 扩展场景:带参数的客服路径
|
||||
assert.strictEqual(
|
||||
testCustomerServiceMatcher({
|
||||
type: 'image',
|
||||
url: '/pages/chat/index?from=user_center',
|
||||
src: '/storage/demo.png'
|
||||
}),
|
||||
true,
|
||||
'带查询参数的 chat 路径必须识别为客服'
|
||||
);
|
||||
|
||||
// 边界场景:普通商品推广项不可误判
|
||||
assert.strictEqual(
|
||||
testCustomerServiceMatcher({
|
||||
type: 'image',
|
||||
url: '/pages/commission/goods',
|
||||
src: '/storage/decorate/20221115/f09ef51624e48d7d8c58cd602110c46e.png'
|
||||
}),
|
||||
false,
|
||||
'普通导航项(如推广商品)绝不可误判为客服'
|
||||
);
|
||||
|
||||
// 边界场景:空对象或未配置
|
||||
assert.strictEqual(testCustomerServiceMatcher({}), false, '空数据对象不可误判为客服');
|
||||
console.log('✅ 测试点 2 通过: 客服识别算法正常、边界与防误判逻辑全部通过');
|
||||
|
||||
// 3. 验证 detail-tabbar.vue
|
||||
const detailTabbarPath = path.resolve(__dirname, '../uniapp/pages/goods/components/detail/detail-tabbar.vue');
|
||||
assert(fs.existsSync(detailTabbarPath), 'detail-tabbar.vue 文件必须存在');
|
||||
const detailTabbarContent = fs.readFileSync(detailTabbarPath, 'utf-8');
|
||||
|
||||
assert(detailTabbarContent.includes('#ifdef MP-WEIXIN'), 'detail-tabbar.vue 必须包含小程序条件编译');
|
||||
assert(detailTabbarContent.includes('open-type="contact"'), 'detail-tabbar.vue 客服入口必须包含 open-type="contact"');
|
||||
assert(detailTabbarContent.includes('show-message-card'), 'detail-tabbar.vue 必须支持发送商品详情卡片给客服');
|
||||
console.log('✅ 测试点 3 通过: 商品详情页底部客服按钮及商品卡片透传配置正确');
|
||||
|
||||
// 4. 验证 s-menu-tools.vue
|
||||
const menuToolsPath = path.resolve(__dirname, '../uniapp/sheep/components/s-menu-tools/s-menu-tools.vue');
|
||||
assert(fs.existsSync(menuToolsPath), 's-menu-tools.vue 文件必须存在');
|
||||
const menuToolsContent = fs.readFileSync(menuToolsPath, 'utf-8');
|
||||
|
||||
assert(menuToolsContent.includes('#ifdef MP-WEIXIN'), 's-menu-tools.vue 必须包含小程序条件编译');
|
||||
assert(menuToolsContent.includes('open-type="contact"'), 's-menu-tools.vue 客服项必须支持 open-type="contact"');
|
||||
console.log('✅ 测试点 4 通过: 全局快捷工具菜单客服直达配置正确');
|
||||
|
||||
// 5. 验证 feedback.vue
|
||||
const feedbackPath = path.resolve(__dirname, '../uniapp/pages/public/feedback.vue');
|
||||
assert(fs.existsSync(feedbackPath), 'feedback.vue 文件必须存在');
|
||||
const feedbackContent = fs.readFileSync(feedbackPath, 'utf-8');
|
||||
|
||||
assert(feedbackContent.includes('open-type="contact"'), 'feedback.vue 必须支持小程序原生客服');
|
||||
console.log('✅ 测试点 5 通过: 意见反馈页客服直达配置正确');
|
||||
|
||||
// 6. 验证 aftersale 页面
|
||||
const aftersaleApplyPath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/apply.vue');
|
||||
const aftersaleDetailPath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/detail.vue');
|
||||
assert(fs.existsSync(aftersaleApplyPath) && fs.existsSync(aftersaleDetailPath), '售后页面必须存在');
|
||||
assert(fs.readFileSync(aftersaleApplyPath, 'utf-8').includes('open-type="contact"'), '售后申请页必须支持小程序客服');
|
||||
assert(fs.readFileSync(aftersaleDetailPath, 'utf-8').includes('open-type="contact"'), '售后详情页必须支持小程序客服');
|
||||
console.log('✅ 测试点 6 通过: 售后申请与售后详情页客服直达配置正确');
|
||||
|
||||
// 7. 验证 chat/index.vue 兜底接入组件
|
||||
const chatIndexPath = path.resolve(__dirname, '../uniapp/pages/chat/index.vue');
|
||||
assert(fs.existsSync(chatIndexPath), 'chat/index.vue 文件必须存在');
|
||||
const chatIndexContent = fs.readFileSync(chatIndexPath, 'utf-8');
|
||||
assert(chatIndexContent.includes('mp-service-bar'), 'chat/index.vue 必须包含微信小程序客服直通卡片');
|
||||
assert(chatIndexContent.includes('open-type="contact"'), 'chat/index.vue 必须支持 open-type="contact"');
|
||||
console.log('✅ 测试点 7 通过: 客服主页面微信小程序客服快速接入口已就绪');
|
||||
|
||||
console.log('\n🎉 所有测试用例执行完毕,全部校验通过!');
|
||||
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<s-layout class="chat-wrap" title="客服" navbar="inner">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="mp-service-bar ss-flex ss-row-between ss-col-center">
|
||||
<view class="ss-flex ss-col-center">
|
||||
<text class="cicon-wechat ss-m-r-12 mp-wechat-icon"></text>
|
||||
<view class="mp-service-text">微信小程序官方客服</view>
|
||||
</view>
|
||||
<button class="ss-reset-button mp-service-btn" open-type="contact">点击接入</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<div class="status">
|
||||
{{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,请检查网络后刷新重试' }}
|
||||
{{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,可使用上方微信客服直接沟通' }}
|
||||
</div>
|
||||
<div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div>
|
||||
<view class="chat-box" :style="{ height: pageHeight + 'px' }">
|
||||
@@ -597,7 +606,36 @@
|
||||
// .ui-navbar-box {
|
||||
// background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
|
||||
// }
|
||||
// }
|
||||
.mp-service-bar {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
z-index: 4;
|
||||
margin: 16rpx 20rpx;
|
||||
padding: 16rpx 24rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 14rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
.mp-wechat-icon {
|
||||
font-size: 40rpx;
|
||||
color: #07c160;
|
||||
}
|
||||
|
||||
.mp-service-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.mp-service-btn {
|
||||
padding: 10rpx 24rpx;
|
||||
background: #07c160;
|
||||
color: #ffffff;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
border-radius: 28rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
position: relative;
|
||||
|
||||
@@ -24,6 +24,25 @@
|
||||
<view class="item-title">收藏</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
v-if="serviceIcon"
|
||||
class="ss-reset-button detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
open-type="contact"
|
||||
:send-message-title="modelValue.title"
|
||||
:send-message-path="'/pages/goods/index?id=' + modelValue.id"
|
||||
:send-message-img="sheep.$url.cdn(modelValue.image)"
|
||||
show-message-card
|
||||
>
|
||||
<image
|
||||
class="item-icon"
|
||||
:src="sheep.$url.static('/assets/addons/shopro/uniapp/goods/message.png')"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<view class="item-title">客服</view>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view
|
||||
v-if="serviceIcon"
|
||||
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
@@ -36,6 +55,7 @@
|
||||
></image>
|
||||
<view class="item-title">客服</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view
|
||||
v-if="shareIcon"
|
||||
class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
|
||||
@@ -154,6 +174,15 @@
|
||||
|
||||
.detail-tabbar-item {
|
||||
width: 100rpx;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
width: 40rpx;
|
||||
|
||||
@@ -57,7 +57,12 @@
|
||||
<su-fixed bottom placeholder>
|
||||
<view class="foot-wrap">
|
||||
<view class="foot_box ss-flex ss-col-center ss-row-between ss-p-x-30">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="ss-reset-button contcat-btn" open-type="contact">联系客服</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="ss-reset-button contcat-btn" @tap="sheep.$router.go('/pages/chat/index')">联系客服</button>
|
||||
<!-- #endif -->
|
||||
<button class="ss-reset-button ui-BG-Main-Gradient sub-btn" @tap="submit">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -115,9 +115,14 @@
|
||||
@tap="onDelete(state.info.id)"
|
||||
>删除</button
|
||||
> -->
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="ss-reset-button contcat-btn btn" open-type="contact">联系客服</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="ss-reset-button contcat-btn btn" @tap="sheep.$router.go('/pages/chat/index')"
|
||||
>联系客服</button
|
||||
>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</su-fixed>
|
||||
</s-layout>
|
||||
|
||||
@@ -51,9 +51,16 @@
|
||||
</uni-forms>
|
||||
<su-fixed bottom placeholder>
|
||||
<view class="ss-flex ss-row-between ss-p-x-30 ss-p-y-10">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button class="kefu-btn ss-reset-button" open-type="contact">
|
||||
联系客服
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button class="kefu-btn ss-reset-button" @tap="sheep.$router.go('/pages/chat/index')">
|
||||
联系客服
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<button class="submit-btn ss-reset-button ui-BG-Main ui-Shadow-Main" @tap="onSubmit">
|
||||
提交
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,33 @@
|
||||
>
|
||||
{{ data.text }}
|
||||
</view>
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
v-if="data.type === 'image' && isCustomerService"
|
||||
:style="[{ width: width }]"
|
||||
class="ss-reset-button menu-icon-wrap ss-flex ss-row-center ss-col-center"
|
||||
open-type="contact"
|
||||
>
|
||||
<image
|
||||
class="nav-image"
|
||||
:src="data.src && data.src.startsWith('/static/') ? data.src : sheep.$url.cdn(data.src)"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</button>
|
||||
<view
|
||||
v-else-if="data.type === 'image'"
|
||||
:style="[{ width: width }]"
|
||||
class="menu-icon-wrap ss-flex ss-row-center ss-col-center"
|
||||
@tap="sheep.$router.go(data.url)"
|
||||
>
|
||||
<image
|
||||
class="nav-image"
|
||||
:src="data.src && data.src.startsWith('/static/') ? data.src : sheep.$url.cdn(data.src)"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view
|
||||
v-if="data.type === 'image'"
|
||||
:style="[{ width: width }]"
|
||||
@@ -19,6 +46,7 @@
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<view class="ss-flex-1" v-if="data.type == 'search'" :style="[{ width: width }]">
|
||||
<s-search-block
|
||||
:placeholder="data.placeholder || '搜索关键字'"
|
||||
@@ -49,6 +77,20 @@
|
||||
});
|
||||
|
||||
const height = computed(() => sheep.$platform.capsule.height);
|
||||
|
||||
// 判断是否为客服入口
|
||||
const isCustomerService = computed(() => {
|
||||
const url = props.data?.url || '';
|
||||
const src = props.data?.src || '';
|
||||
return (
|
||||
url === '/pages/chat/index' ||
|
||||
url.startsWith('/pages/chat') ||
|
||||
url.includes('chat') ||
|
||||
url.includes('kefu') ||
|
||||
url.includes('customer_service') ||
|
||||
src.includes('c47b048175b325b1e78f837a3b696794')
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -61,6 +103,16 @@
|
||||
.menu-icon-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-image {
|
||||
height: 30px;
|
||||
max-width: 100%;
|
||||
|
||||
@@ -6,12 +6,31 @@
|
||||
<view class="container-list ss-flex ss-flex-wrap">
|
||||
<view class="list-item ss-m-b-24" v-for="item in list" :key="item.title">
|
||||
<view class="ss-flex-col ss-col-center">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<button
|
||||
v-if="item.url === '/pages/chat/index'"
|
||||
class="ss-reset-button list-image ss-flex ss-row-center ss-col-center"
|
||||
open-type="contact"
|
||||
@tap="closeMenuTools"
|
||||
>
|
||||
<image v-if="show" :src="sheep.$url.static(item.icon)" class="list-icon"></image>
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="ss-reset-button list-image ss-flex ss-row-center ss-col-center"
|
||||
@tap="onClick(item)"
|
||||
>
|
||||
<image v-if="show" :src="sheep.$url.static(item.icon)" class="list-icon"></image>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<button
|
||||
class="ss-reset-button list-image ss-flex ss-row-center ss-col-center"
|
||||
@tap="onClick(item)"
|
||||
>
|
||||
<image v-if="show" :src="sheep.$url.static(item.icon)" class="list-icon"></image>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
<view class="list-title ss-m-t-20">{{ item.title }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
在新工单中引用
屏蔽一个用户