feat(auth): compact agreement layout to single-line and support flexible backend configuration for user agreement and privacy policy

这个提交包含在:
xcbyy
2026-10-12 09:20:40 +08:00
父节点 f899829000
当前提交 334b7a63b0
共有 6 个文件被更改,包括 161 次插入 和 34 次删除

查看文件

@@ -86,27 +86,43 @@
</div>
</a-tab-pane>
<!-- Tab 3: 用户服务协议与规范 -->
<!-- Tab 3: 用户服务协议与隐私指引配置 -->
<a-tab-pane key="platform_protocol" title="平台规范协议">
<div class="tab-pane-content">
<a-alert type="success" style="margin-bottom: 20px">
在此配置“平台规范与服务协议”弹层的协议条款与合规内容,严格遵循微信内容安全风控标准。
在此灵活配置小程序端的《用户服务协议》与《隐私保护指引》,支持独立条款配置与全平台动态更新,严格遵循微信内容安全风控标准。
</a-alert>
<a-form :model="protoForm" layout="vertical" style="max-width: 800px">
<a-form-item label="协议标题">
<a-input v-model="protoForm.valueJson.title" placeholder="如:用户服务协议与隐私规范" />
</a-form-item>
<a-form-item label="协议摘要概要">
<a-form :model="protoForm" layout="vertical" style="max-width: 860px">
<a-card title="1. 用户服务协议配置 (User Agreement)" :bordered="true" style="margin-bottom: 20px; background: var(--color-bg-2);">
<a-form-item label="协议标题">
<a-input v-model="protoForm.valueJson.userAgreementTitle" placeholder="如:用户服务协议" />
</a-form-item>
<a-form-item label="服务协议全文条款">
<a-textarea
v-model="protoForm.valueJson.userAgreementContent"
placeholder="请输入用户服务协议详细条款,包含账号注册规范、资金托管、现场核销、争议仲裁等"
:auto-size="{ minRows: 6, maxRows: 12 }"
/>
</a-form-item>
</a-card>
<a-card title="2. 隐私保护指引政策配置 (Privacy Policy)" :bordered="true" style="margin-bottom: 20px; background: var(--color-bg-2);">
<a-form-item label="指引标题">
<a-input v-model="protoForm.valueJson.privacyPolicyTitle" placeholder="如:隐私保护指引" />
</a-form-item>
<a-form-item label="隐私保护政策正文">
<a-textarea
v-model="protoForm.valueJson.privacyPolicyContent"
placeholder="请输入微信小程序隐私保护政策正文,包含手机号与位置信息安全加密存储、权限说明等"
:auto-size="{ minRows: 6, maxRows: 12 }"
/>
</a-form-item>
</a-card>
<a-form-item label="协议摘要概要 (对外简述)">
<a-input v-model="protoForm.valueJson.summary" placeholder="协议概要,一两句话总结" />
</a-form-item>
<a-form-item label="服务协议全文内容">
<a-textarea
v-model="protoForm.valueJson.content"
placeholder="请输入完整协议条款"
:auto-size="{ minRows: 8, maxRows: 15 }"
/>
</a-form-item>
<a-form-item label="启用状态">
<a-switch
:checked-value="1"
@@ -627,6 +643,10 @@ const protoForm = reactive({
valueJson: {
title: '用户服务协议与隐私规范',
summary: '严禁发布任何违规言论及低俗诈骗行为,平台全面遵循微信内容安全风控与用户隐私保护标准。',
userAgreementTitle: '用户服务协议',
userAgreementContent: '1. 账号使用规范:用户注册与使用同城生活陪伴服务须符合国家法律法规,严禁利用平台从事任何违法违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,碰面出示4位核销码确认后自动结算至大神钱包。\n3. 履约安全守则:所有同城活动须在合规公开商业街区进行,严禁私下导流与脱离平台交易。\n4. 争议与售后仲裁:若遇服务不符或搭子违约,可随时在订单中发起申诉,由平台运营仲裁专员裁决。',
privacyPolicyTitle: '隐私保护指引',
privacyPolicyContent: '1. 信息收集范围:为保障同城陪玩交易安全与现场4位核销码核验,平台在获得您授权后会依法收集手机号与必要位置信息。\n2. 数据加密保护:平台全面接入微信小程序官方安全风控规范,所有用户数据均实施高强度加密脱敏存储。\n3. 权限撤回与管理:您可随时在小程序系统设置中撤回位置或通讯授权,平台未经您的明确许可绝不会向任何第三方泄露您的隐私数据。',
content: ''
}
});

查看文件

@@ -49,13 +49,13 @@
</view>
</view>
<!-- 底部合规声明与服务协议勾选 -->
<!-- 底部合规声明与服务协议勾选 (单行紧凑排列) -->
<view class="protocol-section" @click="agreedProtocol = !agreedProtocol">
<view class="checkbox" :class="{ checked: agreedProtocol }">
<view class="checkbox-inner" v-if="agreedProtocol"></view>
</view>
<view class="protocol-text">
<text>登录注册即代表您已阅读并同意</text>
<text>登录注册即同意</text>
<text class="protocol-link" @click.stop="openUserAgreement">《用户服务协议》</text>
<text>与</text>
<text class="protocol-link" @click.stop="openPrivacyAgreement">《隐私保护指引》</text>
@@ -69,6 +69,7 @@ import { ref, onMounted } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { ICONS } from '@/common/icons.js';
import { getCurrentThemeKey, applyThemeToNativeUI } from '@/common/theme.js';
import { getPlatformConfigs } from '@/common/api.js';
const APP_LOGO = 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=200';
const DEFAULT_AVATAR = 'https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?w=200';
@@ -77,6 +78,35 @@ const currentThemeKey = ref(getCurrentThemeKey());
const agreedProtocol = ref(true);
const redirectUrl = ref('');
// 动态协议与隐私政策配置 (支持管理后台灵活自定义配置与动态热更新)
const platformProtocol = ref({
userAgreementTitle: '用户服务协议',
userAgreementContent: '1. 账号使用规范:用户注册与使用同城生活陪伴服务须符合国家法律法规,严禁利用平台从事任何违法违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,碰面出示4位核销码确认后自动结算至大神钱包。\n3. 履约安全守则:所有同城活动须在合规公开商业街区进行,严禁私下导流与脱离平台交易。\n4. 争议与售后仲裁:若遇服务不符或搭子违约,可随时在订单中发起申诉,由平台运营仲裁专员裁决。',
privacyPolicyTitle: '隐私保护指引',
privacyPolicyContent: '1. 信息收集范围:为保障同城陪玩交易安全与现场4位核销码核验,平台在获得您授权后会依法收集手机号与必要位置信息。\n2. 数据加密保护:平台全面接入微信小程序官方安全风控规范,所有用户数据均实施高强度加密脱敏存储。\n3. 权限撤回与管理:您可随时在小程序系统设置中撤回位置或通讯授权,平台未经您的明确许可绝不会向任何第三方泄露您的隐私数据。'
});
const loadProtocolConfig = async () => {
try {
const res = await getPlatformConfigs();
if (res && res.platform_protocol) {
const p = res.platform_protocol;
if (typeof p === 'object') {
if (p.userAgreementTitle) platformProtocol.value.userAgreementTitle = p.userAgreementTitle;
if (p.userAgreementContent || p.content) {
platformProtocol.value.userAgreementContent = p.userAgreementContent || p.content;
}
if (p.privacyPolicyTitle) platformProtocol.value.privacyPolicyTitle = p.privacyPolicyTitle;
if (p.privacyPolicyContent) {
platformProtocol.value.privacyPolicyContent = p.privacyPolicyContent;
}
}
}
} catch (err) {
console.warn('[Login] 动态拉取后台协议配置降级:', err);
}
};
const DEMO_ACCOUNTS = [
{
id: 1001,
@@ -117,11 +147,13 @@ onLoad((options) => {
onMounted(() => {
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
loadProtocolConfig();
});
onShow(() => {
currentThemeKey.value = getCurrentThemeKey();
applyThemeToNativeUI(currentThemeKey.value);
loadProtocolConfig();
});
const completeLogin = (userId, customData = {}) => {
@@ -218,8 +250,8 @@ const loginWithDemo = (acc) => {
const openUserAgreement = () => {
uni.showModal({
title: '用户服务协议',
content: '欢迎使用同城生活陪伴服务平台。平台遵循合法合规与安全原则,所有同城活动履约均须在公开透明街区展开,严禁发布违规涉黄及私下交易导流信息。',
title: platformProtocol.value.userAgreementTitle || '用户服务协议',
content: platformProtocol.value.userAgreementContent,
showCancel: false,
confirmText: '我知道了'
});
@@ -227,8 +259,8 @@ const openUserAgreement = () => {
const openPrivacyAgreement = () => {
uni.showModal({
title: '隐私保护指引',
content: '平台高度重视用户个人信息安全,全面遵守国家网信办与微信小程序开发者隐私规范要求。您的手机号与身份信息仅用于平台核销和交易安全保障,未经授权绝不对外公开。',
title: platformProtocol.value.privacyPolicyTitle || '隐私保护指引',
content: platformProtocol.value.privacyPolicyContent,
showCancel: false,
confirmText: '我知道了'
});
@@ -437,20 +469,23 @@ const openPrivacyAgreement = () => {
}
}
/* 协议勾选 */
/* 协议勾选 (一行紧凑完整展示) */
.protocol-section {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
gap: 10rpx;
margin-top: auto;
padding: 16rpx 0;
padding: 20rpx 0;
width: 100%;
box-sizing: border-box;
flex-wrap: nowrap;
.checkbox {
width: 32rpx;
height: 32rpx;
border-radius: 8rpx;
border: 2px solid #64748B;
width: 28rpx;
height: 28rpx;
border-radius: 6rpx;
border: 2rpx solid #64748B;
display: flex;
align-items: center;
justify-content: center;
@@ -461,18 +496,22 @@ const openPrivacyAgreement = () => {
border-color: #07C160;
.checkbox-inner {
width: 12rpx;
height: 12rpx;
width: 10rpx;
height: 10rpx;
background: #FFFFFF;
border-radius: 3rpx;
border-radius: 2rpx;
}
}
}
.protocol-text {
font-size: 22rpx;
font-size: 20rpx;
color: #94A3B8;
line-height: 1.5;
line-height: 1.2;
white-space: nowrap;
display: flex;
align-items: center;
flex-shrink: 0;
.protocol-link {
color: #07C160;

查看文件

@@ -820,9 +820,11 @@ const openAbout = () => {
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: proto.content || proto.summary || '严禁发布任何违规言论及低俗诈骗行为,平台全面遵循微信内容安全风控与用户隐私保护标准。',
content: fullText,
showCancel: false
});
}

查看文件

@@ -770,7 +770,11 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
const protocolVal = JSON.stringify({
title: '用户服务协议与隐私规范',
summary: '严禁发布任何违规言论及低俗诈骗行为,平台全面遵循微信内容安全风控与用户隐私保护标准。',
content: '1. 账号使用规范:用户注册与使用陪玩服务须符合国家法律法规,严禁利用平台从事涉黄、赌博、欺诈等违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,核销确认后自动结算至大神钱包。\n3. 争议与售后:若遇服务不符或搭子违约,可随时提交争议仲裁由运营平台人工介入裁决。'
userAgreementTitle: '用户服务协议',
userAgreementContent: '1. 账号使用规范:用户注册与使用同城生活陪伴服务须符合国家法律法规,严禁利用平台从事任何违法违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,碰面出示4位核销码确认后自动结算至大神钱包。\n3. 履约安全守则:所有同城活动须在合规公开商业街区进行,严禁私下导流与脱离平台交易。\n4. 争议与售后仲裁:若遇服务不符或搭子违约,可随时在订单中发起申诉,由平台运营仲裁专员裁决。',
privacyPolicyTitle: '隐私保护指引',
privacyPolicyContent: '1. 信息收集范围:为保障同城陪玩交易安全与现场4位核销码核验,平台在获得您授权后会依法收集手机号与必要位置信息。\n2. 数据加密保护:平台全面接入微信小程序官方安全风控规范,所有用户数据均实施高强度加密脱敏存储。\n3. 权限撤回与管理:您可随时在小程序系统设置中撤回位置或通讯授权,平台未经您的明确许可绝不会向任何第三方泄露您的隐私数据。',
content: '1. 账号使用规范:用户注册与使用陪玩服务须符合国家法律法规,严禁利用平台从事违法违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,核销确认后自动结算至大神钱包。\n3. 争议与售后:若遇服务不符或搭子违约,可随时提交争议仲裁由运营平台人工介入裁决。'
});
await conn.query(`
@@ -845,5 +849,29 @@ async function seedInitialDataIfEmpty(conn: any): Promise<void> {
VALUES ('activity_rules', '同城组局发单与预算预设规范', ?, '招募席位上下限、快捷单人赏金预设与包场预算梯队', 1)
`, [activityRulesVal]);
}
if (existingKeys.has('platform_protocol')) {
const [protoRows]: any = await conn.query("SELECT value_json FROM platform_configs WHERE config_key = 'platform_protocol'");
if (protoRows && protoRows.length > 0) {
let curVal = protoRows[0].value_json;
if (typeof curVal === 'string') {
try { curVal = JSON.parse(curVal); } catch (_) {}
}
if (curVal && (!curVal.userAgreementContent || !curVal.privacyPolicyContent)) {
const defaultProto = {
userAgreementTitle: '用户服务协议',
userAgreementContent: '1. 账号使用规范:用户注册与使用同城生活陪伴服务须符合国家法律法规,严禁利用平台从事任何违法违规行为。\n2. 资金与交易保障:平台提供全额担保交易,订单完成前款项由官方账户托管,碰面出示4位核销码确认后自动结算至大神钱包。\n3. 履约安全守则:所有同城活动须在合规公开商业街区进行,严禁私下导流与脱离平台交易。\n4. 争议与售后仲裁:若遇服务不符或搭子违约,可随时在订单中发起申诉,由平台运营仲裁专员裁决。',
privacyPolicyTitle: '隐私保护指引',
privacyPolicyContent: '1. 信息收集范围:为保障同城陪玩交易安全与现场4位核销码核验,平台在获得您授权后会依法收集手机号与必要位置信息。\n2. 数据加密保护:平台全面接入微信小程序官方安全风控规范,所有用户数据均实施高强度加密脱敏存储。\n3. 权限撤回与管理:您可随时在小程序系统设置中撤回位置或通讯授权,平台未经您的明确许可绝不会向任何第三方泄露您的隐私数据。'
};
const mergedProto = {
...defaultProto,
...curVal
};
if (!mergedProto.userAgreementContent) mergedProto.userAgreementContent = defaultProto.userAgreementContent;
if (!mergedProto.privacyPolicyContent) mergedProto.privacyPolicyContent = defaultProto.privacyPolicyContent;
await conn.query("UPDATE platform_configs SET value_json = ? WHERE config_key = 'platform_protocol'", [JSON.stringify(mergedProto)]);
}
}
}
}
}

查看文件

@@ -473,6 +473,15 @@ describe('ContentSecurityService & Compliance Architecture (TDD 微信内容安
const emojiRegex = /(?:\p{Extended_Pictographic}|\p{Emoji_Presentation}|[\u2600-\u27BF])/u;
expect(emojiRegex.test(content)).toBe(false);
});
it('登录页面底部协议必须支持后台灵活配置与单行紧凑展示规范', () => {
const loginPath = path.join(rootDir, 'pwuniapp', 'pages', 'auth', 'login.vue');
const content = fs.readFileSync(loginPath, 'utf-8');
expect(content).toContain('getPlatformConfigs');
expect(content).toContain('platformProtocol');
expect(content).toContain('loadProtocolConfig');
expect(content).toContain('white-space: nowrap');
});
});
});

查看文件

@@ -126,4 +126,33 @@ describe('平台通用配置与合规管理 TDD 自动化测试', () => {
const check = secService.checkText(JSON.stringify(faqList));
expect(check.pass).toBe(true);
});
it('8. 用户服务协议与隐私保护指引双独立配置结构与合规内容审查', () => {
const protocolConfig = {
title: '用户服务协议与隐私规范',
summary: '平台合规履约协议与隐私保护规范',
userAgreementTitle: '用户服务协议',
userAgreementContent: '1. 账号使用规范:严禁利用平台从事低俗违规与欺诈交易行为。\n2. 资金保障:平台提供全额担保交易,订单完成前款项由官方账户托管。',
privacyPolicyTitle: '隐私保护指引',
privacyPolicyContent: '1. 信息收集范围:依法收集手机号与位置信息。\n2. 数据加密保护:实施高强度加密脱敏存储。'
};
expect(protocolConfig.userAgreementTitle).toBe('用户服务协议');
expect(protocolConfig.privacyPolicyTitle).toBe('隐私保护指引');
expect(protocolConfig.userAgreementContent).toContain('全额担保交易');
expect(protocolConfig.privacyPolicyContent).toContain('加密脱敏存储');
const check = secService.checkText(JSON.stringify(protocolConfig));
expect(check.pass).toBe(true);
});
it('9. 协议内容包含涉黄或违规敏感词时应触发微信安全拦截', () => {
const badProtocol = {
userAgreementTitle: '用户服务协议',
userAgreementContent: '支持线下提供上门特殊服务,私下转账可打折免抽成'
};
const badCheck = secService.checkText(JSON.stringify(badProtocol));
expect(badCheck.pass).toBe(false);
expect(badCheck.code).toBe(87014);
});
});