diff --git a/miniprogram/package.json b/miniprogram/package.json index 76c631f..744f6d0 100644 --- a/miniprogram/package.json +++ b/miniprogram/package.json @@ -11,7 +11,8 @@ "verify:tabbar": "node scripts/verify-venue-tabbar.mjs", "verify:startup": "node scripts/verify-venue-startup.mjs", "verify:home-switch": "node scripts/verify-home-venue-switch.mjs", - "verify:comprehensive-auth": "node scripts/verify-comprehensive-auth-state.mjs" + "verify:comprehensive-auth": "node scripts/verify-comprehensive-auth-state.mjs", + "verify:auth-agreements": "node scripts/verify-auth-agreements.mjs" }, "dependencies": { "@dcloudio/uni-app": "3.0.0-alpha-5000120260211001", diff --git a/miniprogram/scripts/verify-auth-agreements.mjs b/miniprogram/scripts/verify-auth-agreements.mjs new file mode 100644 index 0000000..23dab4d --- /dev/null +++ b/miniprogram/scripts/verify-auth-agreements.mjs @@ -0,0 +1,100 @@ +import { existsSync, readFileSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const srcRoot = resolve(root, 'src') +const failures = [] + +const read = (relativePath) => readFileSync(resolve(srcRoot, relativePath), 'utf8') + +const expectFile = (relativePath) => { + const path = resolve(srcRoot, relativePath) + if (!existsSync(path)) { + failures.push(`${relativePath} is missing`) + return '' + } + return readFileSync(path, 'utf8') +} + +const expectIncludes = (content, expected, label) => { + if (!content.includes(expected)) failures.push(`${label} is missing: ${expected}`) +} + +const expectExcludes = (content, unexpected, label) => { + if (content.includes(unexpected)) failures.push(`${label} should not contain: ${unexpected}`) +} + +const agreements = expectFile('components/AgreementLinks.vue') +expectIncludes(agreements, '用户协议', 'AgreementLinks user agreement link') +expectIncludes(agreements, '隐私政策', 'AgreementLinks privacy policy link') +expectIncludes(agreements, 'u-popup', 'AgreementLinks popup') +expectIncludes(agreements, '场馆小程序用户服务协议', 'AgreementLinks user agreement content') +expectIncludes(agreements, '场馆小程序隐私政策', 'AgreementLinks privacy policy content') +expectIncludes(agreements, 'defineExpose', 'AgreementLinks exposed popup methods') + +const unauthPanel = expectFile('components/UnauthLoginPanel.vue') +expectIncludes(unauthPanel, '/static/brand/logo.png', 'UnauthLoginPanel brand logo') +expectIncludes(unauthPanel, '大兴区冰上运动中心', 'UnauthLoginPanel app name') +expectIncludes(unauthPanel, 'brand-section', 'UnauthLoginPanel brand section') +expectIncludes(unauthPanel, 'login-card', 'UnauthLoginPanel login card') +expectIncludes(unauthPanel, 'justify-content: center;', 'UnauthLoginPanel centered layout') +expectIncludes(unauthPanel, 'calc(100vh - 176rpx - env(safe-area-inset-bottom))', 'UnauthLoginPanel available viewport height') +expectIncludes(unauthPanel, 'align-self: center;', 'UnauthLoginPanel centered agreement rows') +expectIncludes(unauthPanel, 'hasAcceptedUserAgreement = ref(false)', 'UnauthLoginPanel user agreement manual state') +expectIncludes(unauthPanel, 'hasAcceptedPrivacyPolicy = ref(false)', 'UnauthLoginPanel privacy policy manual state') +expectIncludes(unauthPanel, 'canLogin', 'UnauthLoginPanel login gate') +expectIncludes(unauthPanel, 'acceptance-user', 'UnauthLoginPanel user agreement checkbox') +expectIncludes(unauthPanel, 'acceptance-privacy', 'UnauthLoginPanel privacy policy checkbox') +expectIncludes(unauthPanel, "emit('login')", 'UnauthLoginPanel login emit') +expectIncludes(unauthPanel, '请先勾选用户协议和隐私政策', 'UnauthLoginPanel unchecked feedback') +expectIncludes(unauthPanel, 'AgreementLinks', 'UnauthLoginPanel shared agreement popup') +expectExcludes(unauthPanel, '预约课程、查看', 'UnauthLoginPanel extra marketing copy') +expectExcludes(unauthPanel, '冰上课程', 'UnauthLoginPanel extra feature chip') +expectExcludes(unauthPanel, '预约行程', 'UnauthLoginPanel extra feature chip') +expectExcludes(unauthPanel, '会员权益', 'UnauthLoginPanel extra feature chip') +expectExcludes(unauthPanel, '登录仅用于', 'UnauthLoginPanel extra safe tip') +expectExcludes(unauthPanel, 'brand-logo-shell', 'UnauthLoginPanel logo shell decoration') +expectExcludes(unauthPanel, 'radial-gradient', 'UnauthLoginPanel background decoration') +expectExcludes(unauthPanel, 'linear-gradient(180deg, #EEF8FF', 'UnauthLoginPanel blue block background') +expectExcludes(unauthPanel, 'background: #FFFFFF;', 'UnauthLoginPanel nested white card background') +expectExcludes(unauthPanel, 'space-between', 'UnauthLoginPanel scattered vertical layout') +expectExcludes(unauthPanel, 'width: 100%;\n max-width: 420rpx;', 'UnauthLoginPanel left-aligned agreement rows') + +const loginPages = [ + ['pages/ice/bookings/index.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/ice/bookings/detail.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/ice/profile/index.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/ice/profile/detail.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/ice/profile/cards.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/ice/profile/card-history.vue', 'v-else-if="sessionStatus === SESSION_STATUS.GUEST"', '@login="onWechatLogin"'], + ['pages/comprehensive/bookings/index.vue', 'v-if="!session.isLoggedIn"', '@login="login"'], + ['pages/comprehensive/profile/index.vue', 'v-if="!session.isLoggedIn"', '@login="login"'] +] + +for (const [page, condition, handler] of loginPages) { + const content = read(page) + expectIncludes(content, ' { for (const page of pages) { const content = readFileSync(resolve(srcRoot, page), 'utf8') expectIncludes(content, 'v-if="!session.isLoggedIn"', `${page} unauth condition`) - expectIncludes(content, ' + + + 用户协议 + | + 隐私政策 + + + + + + {{ currentAgreement.popupTitle }} + + + + + + + {{ currentAgreement.title }} + 更新日期:2026年6月9日 + {{ currentAgreement.intro }} + + {{ section.title }} + {{ item }} + + + + + + + + + + + diff --git a/miniprogram/src/components/UnauthLoginPanel.vue b/miniprogram/src/components/UnauthLoginPanel.vue new file mode 100644 index 0000000..cad25dd --- /dev/null +++ b/miniprogram/src/components/UnauthLoginPanel.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/miniprogram/src/pages/comprehensive/bookings/index.vue b/miniprogram/src/pages/comprehensive/bookings/index.vue index ff57b5b..83e2ddc 100644 --- a/miniprogram/src/pages/comprehensive/bookings/index.vue +++ b/miniprogram/src/pages/comprehensive/bookings/index.vue @@ -1,11 +1,8 @@ @@ -223,6 +220,8 @@ import { api, authStorage } from '@/modules/ice/api/member' import { errorMessage, getMemberSession, loginAndStoreToken, SESSION_STATUS } from '@/modules/ice/api/session' import VenueTabBar from '@/components/VenueTabBar.vue' import StatePanel from '@/components/ice/StatePanel.vue' +import AgreementLinks from '@/components/AgreementLinks.vue' +import UnauthLoginPanel from '@/components/UnauthLoginPanel.vue' const isLoggedIn = ref(false) const isBound = ref(false) diff --git a/miniprogram/src/static/brand/logo.png b/miniprogram/src/static/brand/logo.png new file mode 100644 index 0000000..2e41448 Binary files /dev/null and b/miniprogram/src/static/brand/logo.png differ