const fs = require('fs'); const path = require('path'); let passed = 0; let failed = 0; function assert(condition, testName, detail = '') { if (condition) { console.log(` [PASS] ${testName}`); passed++; } else { console.error(` [FAIL] ${testName} - ${detail}`); failed++; } } console.log("========================================================"); console.log(" 微信小程序默认登录采用手机号一键快捷登录规格测试"); console.log("========================================================\n"); // 1. 后端服务层: MiniProgram.php const miniProgramPhpPath = path.resolve(__dirname, '../addons/shopro/service/third/wechat/MiniProgram.php'); assert(fs.existsSync(miniProgramPhpPath), 'MiniProgram.php 文件存在'); const miniProgramPhpContent = fs.readFileSync(miniProgramPhpPath, 'utf-8'); assert( miniProgramPhpContent.includes('wxa/business/getuserphonenumber') && miniProgramPhpContent.includes("['code']"), 'MiniProgram.php 支持微信新版 code 获取手机号接口', '未包含 wxa/business/getuserphonenumber 逻辑' ); assert( miniProgramPhpContent.includes('encryptor->decryptData') && miniProgramPhpContent.includes('purePhoneNumber'), 'MiniProgram.php 保留传统 iv + encryptedData 解密降级兼容', '未包含 decryptData 解密兼容' ); assert( miniProgramPhpContent.includes('$mobile = $this->getUserPhoneNumber()') && miniProgramPhpContent.includes("'mobile' => $mobile"), 'MiniProgram.php 在 login() 方法中当传入手机号凭证时自动提取 mobile', 'login() 中未提取手机号' ); // 2. 后端服务层: Wechat.php const wechatServicePath = path.resolve(__dirname, '../addons/shopro/service/third/wechat/Wechat.php'); assert(fs.existsSync(wechatServicePath), 'Wechat.php (Service) 文件存在'); const wechatServiceContent = fs.readFileSync(wechatServicePath, 'utf-8'); assert( wechatServiceContent.includes("UserModel::where('mobile', $extend['mobile'])->find()"), 'Wechat.php 在 registerOrBindUser 中检查已有手机号并直接绑定关联', '未检测到已有手机号用户直接关联逻辑' ); assert( wechatServiceContent.includes('$user->mobile = $extend['), 'Wechat.php 为已绑定微信但尚无手机号的用户自动补充手机号', '未检测到已绑定账号补充手机号逻辑' ); // 3. 后端控制器层: Wechat.php const wechatControllerPath = path.resolve(__dirname, '../addons/shopro/controller/third/Wechat.php'); assert(fs.existsSync(wechatControllerPath), 'Wechat.php (Controller) 文件存在'); const wechatControllerContent = fs.readFileSync(wechatControllerPath, 'utf-8'); assert( wechatControllerContent.includes("'token' => $auth->isLogin() ? $auth->getToken() : ''"), 'Wechat.php (Controller) 在 login 成功响应数据中返回 token', '响应未返回 token' ); // 4. 前端平台驱动层: miniProgram.js const miniProgramJsPath = path.resolve(__dirname, '../uniapp/sheep/platform/provider/wechat/miniProgram.js'); assert(fs.existsSync(miniProgramJsPath), 'miniProgram.js 文件存在'); const miniProgramJsContent = fs.readFileSync(miniProgramJsPath, 'utf-8'); assert( miniProgramJsContent.includes('const mobileLogin = async (e) => {') && miniProgramJsContent.includes('third.wechat.login'), 'miniProgram.js 包含封装完善的 mobileLogin 方法', '未包含 mobileLogin 方法' ); assert( miniProgramJsContent.includes('mobileLogin,') && miniProgramJsContent.includes('export default {'), 'miniProgram.js 在 export default 中导出了 mobileLogin', '未导出 mobileLogin' ); // 5. 前端弹窗状态控制: useModal.js const useModalPath = path.resolve(__dirname, '../uniapp/sheep/hooks/useModal.js'); assert(fs.existsSync(useModalPath), 'useModal.js 文件存在'); const useModalContent = fs.readFileSync(useModalPath, 'utf-8'); assert( useModalContent.includes('MP-WEIXIN') && useModalContent.includes("targetType = 'mpMobileLogin'"), 'useModal.js 微信小程序环境下默认弹窗类型为 mpMobileLogin', '未设置微信小程序默认弹窗类型为 mpMobileLogin' ); // 6. 新增组件: mp-mobile-login.vue const mpMobileLoginPath = path.resolve(__dirname, '../uniapp/sheep/components/s-auth-modal/components/mp-mobile-login.vue'); assert(fs.existsSync(mpMobileLoginPath), 'mp-mobile-login.vue 文件存在'); const mpMobileLoginContent = fs.readFileSync(mpMobileLoginPath, 'utf-8'); assert( mpMobileLoginContent.includes('open-type="getPhoneNumber"') && mpMobileLoginContent.includes('@getphonenumber="onGetPhoneNumber"'), 'mp-mobile-login.vue 包含 open-type="getPhoneNumber" 授权按钮', '缺少 getPhoneNumber 授权按钮' ); assert( mpMobileLoginContent.includes("showAuthModal('smsLogin')") && mpMobileLoginContent.includes("showAuthModal('accountLogin')"), 'mp-mobile-login.vue 提供切换短信登录和账号登录的快速入口', '缺少其他登录方式入口' ); assert( mpMobileLoginContent.includes('agreeStatus') && mpMobileLoginContent.includes('mobileLogin'), 'mp-mobile-login.vue 校验用户协议并调用 mobileLogin 登录', '缺少协议校验或登录调用' ); // 7. 弹窗容器: s-auth-modal.vue const sAuthModalPath = path.resolve(__dirname, '../uniapp/sheep/components/s-auth-modal/s-auth-modal.vue'); assert(fs.existsSync(sAuthModalPath), 's-auth-modal.vue 文件存在'); const sAuthModalContent = fs.readFileSync(sAuthModalPath, 'utf-8'); assert( sAuthModalContent.includes("import mpMobileLogin from './components/mp-mobile-login.vue'") && sAuthModalContent.includes("authType === 'mpMobileLogin'"), 's-auth-modal.vue 成功挂载 mp-mobile-login 组件', '未挂载 mp-mobile-login' ); assert( sAuthModalContent.includes("'mpMobileLogin'") && sAuthModalContent.includes('agreement-box'), 's-auth-modal.vue 协议栏支持 mpMobileLogin 显示与勾选状态关联', '协议栏未支持 mpMobileLogin' ); // 8. 个人中心卡片组件: s-user-card.vue const sUserCardPath = path.resolve(__dirname, '../uniapp/sheep/components/s-user-card/s-user-card.vue'); assert(fs.existsSync(sUserCardPath), 's-user-card.vue 文件存在'); const sUserCardContent = fs.readFileSync(sUserCardPath, 'utf-8'); assert( sUserCardContent.includes('#ifdef MP-WEIXIN') && sUserCardContent.includes('open-type="getPhoneNumber"') && sUserCardContent.includes('@getphonenumber="onQuickLogin"'), 's-user-card.vue 在微信小程序端未登录时渲染 open-type="getPhoneNumber" 快捷登录按钮', '未检测到微信小程序快捷登录按钮' ); assert( sUserCardContent.includes('async function onQuickLogin(e)') && sUserCardContent.includes("mobileLogin(e.detail)"), 's-user-card.vue 包含 onQuickLogin 处理函数调用 mobileLogin 完成快捷登录', '未检测到 onQuickLogin 处理逻辑' ); assert( sUserCardContent.includes('hasBoundMobile') && sUserCardContent.includes('userInfo.value?.mobile || userInfo.value?.verification?.mobile'), 's-user-card.vue 使用 hasBoundMobile 判定:只要存在手机号则不展示绑定提示条', '缺少 hasBoundMobile 判定逻辑' ); assert( sUserCardContent.includes('onBindPhoneNumber') && sUserCardContent.includes('bindUserPhoneNumber(e.detail)'), 's-user-card.vue 在微信小程序端去绑定支持 open-type="getPhoneNumber" 一键绑定', '缺少一键去绑定逻辑' ); assert( sUserCardContent.includes('#ifndef MP-WEIXIN') && sUserCardContent.includes('showAuthModal()'), 's-user-card.vue 保留非微信小程序端正常点击弹窗唤醒逻辑', '未保留非微信小程序端唤醒逻辑' ); // 9. 用户模型与用户仓库:User.php & user.js const userModelPath = path.resolve(__dirname, '../application/admin/model/shopro/user/User.php'); assert(fs.existsSync(userModelPath), 'User.php 模型存在'); const userModelContent = fs.readFileSync(userModelPath, 'utf-8'); assert( userModelContent.includes("if (!empty($data['mobile']))") && userModelContent.includes("$value['mobile'] = 1"), 'User.php 模型 getVerificationAttr 当会员已有手机号时自动认定 mobile = 1', '缺少 mobile 存在时自动认定 mobile = 1 逻辑' ); assert( userModelContent.includes('function setVerificationAttr'), 'User.php 模型定义了 setVerificationAttr 支持对象/数组自动 json 序列化', '缺少 setVerificationAttr 方法' ); const userStorePath = path.resolve(__dirname, '../uniapp/sheep/store/user.js'); assert(fs.existsSync(userStorePath), 'user.js Store 存在'); const userStoreContent = fs.readFileSync(userStorePath, 'utf-8'); assert( userStoreContent.includes('if (merged.mobile)') && userStoreContent.includes('mobile: 1'), 'user.js getInfo() 当存在 mobile 时自动认定 verification.mobile = 1', '缺少 getInfo() 补充 verification.mobile 逻辑' ); // 10. 售后申请页编译修复: apply.vue const applyVuePath = path.resolve(__dirname, '../uniapp/pages/order/aftersale/apply.vue'); assert(fs.existsSync(applyVuePath), 'apply.vue 存在'); const applyVueContent = fs.readFileSync(applyVuePath, 'utf-8'); assert( applyVueContent.includes(':modelValue="formData"') && !applyVueContent.includes('v-model="formData"'), 'apply.vue 使用 :modelValue="formData" 消除 const 重赋值编译错误', '仍包含 v-model="formData"' ); assert( applyVueContent.includes('let formData = reactive({'), 'apply.vue 中 formData 使用 let 声明提供双重防护', 'formData 不是 let 声明' ); // 11. bindUserPhoneNumber 弹性支持与自动更新 assert( miniProgramJsContent.includes('bindUserPhoneNumber') && miniProgramJsContent.includes("sheep.$store('user').getInfo()"), 'miniProgram.js bindUserPhoneNumber 成功后自动调用 getInfo() 刷新用户信息', 'bindUserPhoneNumber 未自动刷新用户信息' ); // 12. 个人中心绑定横条受 showBindMobile 精确控制(微信小程序环境不冗余展示) assert( sUserCardContent.includes('v-if="showBindMobile"') && sUserCardContent.includes('#ifdef MP-WEIXIN'), 's-user-card.vue 使用 showBindMobile 精确控制,微信小程序端不显示冗余绑定提示条', '未配置 showBindMobile 精确控制' ); console.log("\n--------------------------------------------------------"); console.log(`测试执行结果: 通过 ${passed} 项, 失败 ${failed} 项`); console.log("========================================================"); process.exit(failed === 0 ? 0 : 1);