diff --git a/pwuniapp/pages.json b/pwuniapp/pages.json
index 5da08f2..3b77df7 100644
--- a/pwuniapp/pages.json
+++ b/pwuniapp/pages.json
@@ -28,6 +28,15 @@
"backgroundColor": "#0A0F1D"
}
},
+ {
+ "path": "pages/common/protocol",
+ "style": {
+ "navigationBarTitleText": "服务协议与隐私政策",
+ "navigationBarBackgroundColor": "#0A0F1D",
+ "navigationBarTextStyle": "white",
+ "backgroundColor": "#0A0F1D"
+ }
+ },
{
"path": "pages/activity/list",
"style": {
diff --git a/pwuniapp/pages/auth/login.vue b/pwuniapp/pages/auth/login.vue
index 8ff274d..bdf2361 100644
--- a/pwuniapp/pages/auth/login.vue
+++ b/pwuniapp/pages/auth/login.vue
@@ -249,20 +249,30 @@ const loginWithDemo = (acc) => {
};
const openUserAgreement = () => {
- uni.showModal({
- title: platformProtocol.value.userAgreementTitle || '用户服务协议',
- content: platformProtocol.value.userAgreementContent,
- showCancel: false,
- confirmText: '我知道了'
+ uni.navigateTo({
+ url: '/pages/common/protocol?type=service',
+ fail: () => {
+ uni.showModal({
+ title: platformProtocol.value.userAgreementTitle || '用户服务协议',
+ content: platformProtocol.value.userAgreementContent,
+ showCancel: false,
+ confirmText: '我知道了'
+ });
+ }
});
};
const openPrivacyAgreement = () => {
- uni.showModal({
- title: platformProtocol.value.privacyPolicyTitle || '隐私保护指引',
- content: platformProtocol.value.privacyPolicyContent,
- showCancel: false,
- confirmText: '我知道了'
+ uni.navigateTo({
+ url: '/pages/common/protocol?type=privacy',
+ fail: () => {
+ uni.showModal({
+ title: platformProtocol.value.privacyPolicyTitle || '隐私保护指引',
+ content: platformProtocol.value.privacyPolicyContent,
+ showCancel: false,
+ confirmText: '我知道了'
+ });
+ }
});
};
diff --git a/pwuniapp/pages/common/protocol.vue b/pwuniapp/pages/common/protocol.vue
new file mode 100644
index 0000000..93d5b49
--- /dev/null
+++ b/pwuniapp/pages/common/protocol.vue
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+ 重要提示与导言:
+ {{ pageSummary }}
+
+
+
+
+
+
+
+ {{ sec.title }}
+
+ {{ sec.body }}
+
+
+
+
+
+ 法律合规声明:
+
+ 平台全面遵守国家网信办相关规定及微信小程序平台运营规范。平台依法对同城履约全链路提供合规安全守护、技术加密保障与7x24小时资金争议仲裁。如您对本协议内容有任何异议,请停止使用平台相关服务。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pwuniapp/pages/user/index.vue b/pwuniapp/pages/user/index.vue
index a9a5257..2fb1d00 100644
--- a/pwuniapp/pages/user/index.vue
+++ b/pwuniapp/pages/user/index.vue
@@ -816,18 +816,26 @@ const openFaq = () => {
};
const openAbout = () => {
- const proto = platformConfigs.value.platform_protocol || {};
- if (privacyModalRef.value && privacyModalRef.value.show) {
- privacyModalRef.value.show();
- } else {
- const fullText = (proto.userAgreementContent ? `【用户服务协议】\n${proto.userAgreementContent}\n\n` : '') +
- (proto.privacyPolicyContent ? `【隐私保护指引】\n${proto.privacyPolicyContent}` : (proto.content || proto.summary || '严禁发布任何违规言论及低俗诈骗行为,平台全面遵循微信内容安全风控与用户隐私保护标准。'));
- uni.showModal({
- title: proto.title || '用户服务协议与隐私规范',
- content: fullText,
- showCancel: false
- });
- }
+ uni.showActionSheet({
+ itemList: ['用户服务协议', '隐私保护指引'],
+ success: (res) => {
+ const type = res.tapIndex === 1 ? 'privacy' : 'service';
+ uni.navigateTo({
+ url: `/pages/common/protocol?type=${type}`,
+ fail: () => {
+ const proto = platformConfigs.value.platform_protocol || {};
+ const fullText = type === 'privacy'
+ ? (proto.privacyPolicyContent || '平台全面遵守微信小程序开发者隐私规范要求,对用户数据实施高强度加密保护。')
+ : (proto.userAgreementContent || '平台遵循合法合规与安全原则,所有同城活动履约均须在公开透明街区展开。');
+ uni.showModal({
+ title: type === 'privacy' ? (proto.privacyPolicyTitle || '隐私保护指引') : (proto.userAgreementTitle || '用户服务协议'),
+ content: fullText,
+ showCancel: false
+ });
+ }
+ });
+ }
+ });
};
const navTo = (url) => {
diff --git a/server/tests/content-security.spec.ts b/server/tests/content-security.spec.ts
index ed91613..c77c632 100644
--- a/server/tests/content-security.spec.ts
+++ b/server/tests/content-security.spec.ts
@@ -130,7 +130,7 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
const raw = fs.readFileSync(pagesJsonPath, 'utf-8');
const config = JSON.parse(raw);
expect(Array.isArray(config.pages)).toBe(true);
- expect(config.pages.length).toBe(15);
+ expect(config.pages.length).toBe(16);
for (const pageItem of config.pages) {
const vueFilePath = path.join(rootDir, 'pwuniapp', `${pageItem.path}.vue`);
@@ -483,6 +483,45 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
expect(content).toContain('white-space: nowrap');
});
});
+
+ describe('13. 专属独立法律协议与隐私政策长文阅读页面验证 (pages/common/protocol.vue)', () => {
+ it('pages.json 必须成功注册 pages/common/protocol 且物理文件必须存在', () => {
+ const protoPath = path.join(rootDir, 'pwuniapp', 'pages', 'common', 'protocol.vue');
+ expect(fs.existsSync(protoPath)).toBe(true);
+ const pagesJsonPath = path.join(rootDir, 'pwuniapp', 'pages.json');
+ const pagesJson = fs.readFileSync(pagesJsonPath, 'utf-8');
+ expect(pagesJson).toContain('pages/common/protocol');
+ expect(pagesJson).toContain('服务协议与隐私政策');
+ });
+
+ it('协议页面必须支持动态章节结构化拆分、长文上下滚动与双主题适配', () => {
+ const protoPath = path.join(rootDir, 'pwuniapp', 'pages', 'common', 'protocol.vue');
+ const content = fs.readFileSync(protoPath, 'utf-8');
+ expect(content).toContain('scroll-view');
+ expect(content).toContain('formattedSections');
+ expect(content).toContain('getPlatformConfigs');
+ expect(content).toContain('applyThemeToNativeUI');
+ expect(content).toContain('theme-warm');
+ });
+
+ it('登录页与个人中心必须实现对专属协议页面的原生导航跳转', () => {
+ const loginPath = path.join(rootDir, 'pwuniapp', 'pages', 'auth', 'login.vue');
+ const loginContent = fs.readFileSync(loginPath, 'utf-8');
+ expect(loginContent).toContain('/pages/common/protocol?type=service');
+ expect(loginContent).toContain('/pages/common/protocol?type=privacy');
+
+ const userPath = path.join(rootDir, 'pwuniapp', 'pages', 'user', 'index.vue');
+ const userContent = fs.readFileSync(userPath, 'utf-8');
+ expect(userContent).toContain('/pages/common/protocol?type=');
+ });
+
+ it('协议阅读页面代码严禁包含任何系统 Emoji', () => {
+ const protoPath = path.join(rootDir, 'pwuniapp', 'pages', 'common', 'protocol.vue');
+ const content = fs.readFileSync(protoPath, 'utf-8');
+ const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
+ expect(emojiRegex.test(content)).toBe(false);
+ });
+ });
});