feat: 添加服务条款页面及相关逻辑,更新用户注册流程以要求同意条款

This commit is contained in:
2026-01-13 23:34:06 +07:00
parent 0e63595941
commit f1f99ad587
9 changed files with 362 additions and 5 deletions

2
components.d.ts vendored
View File

@@ -24,6 +24,7 @@ declare module 'vue' {
IonBadge: typeof import('@ionic/vue')['IonBadge']
IonButton: typeof import('@ionic/vue')['IonButton']
IonButtons: typeof import('@ionic/vue')['IonButtons']
IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
IonCol: typeof import('@ionic/vue')['IonCol']
IonContent: typeof import('@ionic/vue')['IonContent']
IonDatetime: typeof import('@ionic/vue')['IonDatetime']
@@ -83,6 +84,7 @@ declare global {
const IonBadge: typeof import('@ionic/vue')['IonBadge']
const IonButton: typeof import('@ionic/vue')['IonButton']
const IonButtons: typeof import('@ionic/vue')['IonButtons']
const IonCheckbox: typeof import('@ionic/vue')['IonCheckbox']
const IonCol: typeof import('@ionic/vue')['IonCol']
const IonContent: typeof import('@ionic/vue')['IonContent']
const IonDatetime: typeof import('@ionic/vue')['IonDatetime']

View File

@@ -492,5 +492,102 @@
"iosStep3": "3. Tap 'Add' to confirm installation",
"iosNote": "Once installed, you can use it like a native app"
}
},
"term": {
"title": "Terms of Service",
"lastUpdate": "Last Updated: January 2026",
"introduction": "Welcome to our service. Please read the following terms of service carefully before using this platform. By using this service, you agree to be bound by these terms.",
"sections": {
"serviceDescription": {
"title": "Service Description",
"content": {
"0": "This platform provides digital asset trading services, including but not limited to spot trading, futures trading, and other financial services.",
"1": "We are committed to providing users with a secure, stable, and efficient trading environment, but are not responsible for market fluctuations or trading results.",
"2": "The platform reserves the right to modify, suspend, or terminate some or all services at any time without prior notice."
}
},
"userResponsibilities": {
"title": "User Responsibilities",
"content": {
"0": "You must be at least 18 years old or have reached the legal age in your jurisdiction to use this service.",
"1": "You agree to provide truthful, accurate, and complete registration information and update it in a timely manner to maintain accuracy.",
"2": "You are responsible for all activities conducted through your account, including any unauthorized access or use."
}
},
"accountSecurity": {
"title": "Account Security",
"content": {
"0": "You are responsible for maintaining the confidentiality of your account password and other security credentials.",
"1": "We recommend enabling security features such as two-factor authentication to enhance account security.",
"2": "Please notify us immediately if you discover any abnormal account activity or security vulnerabilities."
}
},
"prohibitedActivities": {
"title": "Prohibited Activities",
"content": {
"0": "It is strictly prohibited to use this platform for money laundering, terrorist financing, or other illegal activities.",
"1": "Market price manipulation, fake trading, or any fraudulent behavior is prohibited.",
"2": "You may not use automated programs, bots, or other unauthorized means to access this platform.",
"3": "Violation of the above provisions may result in account freezing or permanent ban."
}
},
"riskDisclosure": {
"title": "Risk Disclosure",
"content": {
"0": "Digital asset trading is highly speculative and volatile, which may result in partial or total loss of funds.",
"1": "Please fully understand market risks before trading and only invest funds you can afford to lose.",
"2": "The platform does not provide investment advice, and you are solely responsible for all trading decisions."
}
},
"privacyPolicy": {
"title": "Privacy Policy",
"content": {
"0": "We value your privacy and take reasonable measures to protect your personal information.",
"1": "We may collect necessary information to provide services, comply with legal requirements, and improve user experience.",
"2": "For detailed privacy policy, please refer to our Privacy Policy page."
}
},
"intellectualProperty": {
"title": "Intellectual Property",
"content": {
"0": "All content on this platform, including but not limited to text, graphics, logos, and software, is protected by intellectual property laws.",
"1": "Without explicit authorization, you may not copy, modify, distribute, or otherwise use platform content."
}
},
"disclaimers": {
"title": "Disclaimers",
"content": {
"0": "This service is provided 'as is' without any express or implied warranties.",
"1": "We are not responsible for service interruptions, data loss, or any indirect damages.",
"2": "We assume no responsibility for third-party services or links."
}
},
"termination": {
"title": "Termination",
"content": {
"0": "We reserve the right to terminate or suspend your account at any time, especially in case of violation of these terms.",
"1": "After account termination, you remain responsible for actions and obligations prior to termination."
}
},
"changes": {
"title": "Changes to Terms",
"content": {
"0": "We may revise these terms of service from time to time, and the revised terms will be published on the platform.",
"1": "Continued use of the service indicates your acceptance of the revised terms."
}
},
"governingLaw": {
"title": "Governing Law",
"content": {
"0": "These terms are governed by applicable law, and any disputes should be resolved through negotiation.",
"1": "If negotiation fails, disputes shall be submitted to a court with jurisdiction."
}
}
},
"contact": {
"title": "Contact Us",
"description": "If you have any questions about these Terms of Service or need assistance, please contact us:",
"email": "Email"
}
}
}

View File

@@ -498,5 +498,102 @@
"iosStep3": "3. 点击添加确认安装",
"iosNote": "安装后可以像原生应用一样使用"
}
},
"term": {
"title": "服务条款",
"lastUpdate": "最后更新2026年1月",
"introduction": "欢迎使用我们的服务。在使用本平台之前,请仔细阅读以下服务条款。使用本服务即表示您同意遵守这些条款。",
"sections": {
"serviceDescription": {
"title": "服务说明",
"content": {
"0": "本平台提供数字资产交易服务,包括但不限于现货交易、期货交易等金融服务。",
"1": "我们致力于为用户提供安全、稳定、高效的交易环境,但不对市场波动或交易结果承担责任。",
"2": "平台保留随时修改、暂停或终止部分或全部服务的权利,恕不另行通知。"
}
},
"userResponsibilities": {
"title": "用户责任",
"content": {
"0": "您必须年满18周岁或达到您所在司法管辖区的法定年龄方可使用本服务。",
"1": "您同意提供真实、准确、完整的注册信息,并及时更新以保持信息的准确性。",
"2": "您对通过您的账户进行的所有活动负责,包括任何未经授权的访问或使用。"
}
},
"accountSecurity": {
"title": "账户安全",
"content": {
"0": "您有责任维护账户密码和其他安全凭证的机密性。",
"1": "建议启用双因素认证等安全功能以提高账户安全性。",
"2": "如发现账户异常活动或安全漏洞,请立即通知我们。"
}
},
"prohibitedActivities": {
"title": "禁止行为",
"content": {
"0": "严禁使用本平台进行洗钱、恐怖融资或其他非法活动。",
"1": "禁止操纵市场价格、进行虚假交易或从事任何欺诈行为。",
"2": "不得使用自动化程序、机器人或其他未经授权的方式访问本平台。",
"3": "违反上述规定可能导致账户被冻结或永久封禁。"
}
},
"riskDisclosure": {
"title": "风险披露",
"content": {
"0": "数字资产交易具有高度投机性和波动性,可能导致部分或全部资金损失。",
"1": "交易前请充分了解市场风险,仅投资您能承受损失的资金。",
"2": "平台不提供投资建议,所有交易决策均由您自行承担责任。"
}
},
"privacyPolicy": {
"title": "隐私政策",
"content": {
"0": "我们重视您的隐私,并采取合理措施保护您的个人信息。",
"1": "我们可能收集必要的信息以提供服务、遵守法律要求和改进用户体验。",
"2": "详细的隐私政策请参阅我们的隐私政策页面。"
}
},
"intellectualProperty": {
"title": "知识产权",
"content": {
"0": "本平台的所有内容,包括但不限于文本、图形、标识、软件,均受知识产权法保护。",
"1": "未经明确授权,不得复制、修改、分发或以其他方式使用平台内容。"
}
},
"disclaimers": {
"title": "免责声明",
"content": {
"0": "本服务按'现状'提供,不作任何明示或暗示的保证。",
"1": "我们不对服务中断、数据丢失或任何间接损失承担责任。",
"2": "对于第三方服务或链接,我们不承担任何责任。"
}
},
"termination": {
"title": "终止服务",
"content": {
"0": "我们保留随时终止或暂停您账户的权利,特别是在违反本条款的情况下。",
"1": "账户终止后,您仍需对终止前的行为和义务负责。"
}
},
"changes": {
"title": "条款变更",
"content": {
"0": "我们可能不时修订本服务条款,修订后的条款将在平台上公布。",
"1": "继续使用服务即表示您接受修订后的条款。"
}
},
"governingLaw": {
"title": "适用法律",
"content": {
"0": "本条款受适用法律管辖,任何争议应通过协商解决。",
"1": "如协商不成,应提交至有管辖权的法院解决。"
}
}
},
"contact": {
"title": "联系我们",
"description": "如果您对本服务条款有任何疑问或需要帮助,请通过以下方式联系我们:",
"email": "电子邮件"
}
}
}

View File

@@ -9,6 +9,10 @@ const routes: Array<RouteRecordRaw> = [
path: "/auth/signup",
component: () => import("@/views/auth/signup/index.vue"),
},
{
path: "/auth/term",
component: () => import("@/views/auth/term.vue"),
},
];
export default routes;

View File

@@ -4,6 +4,7 @@ import type { EmailVerifyClient } from "@/api/types";
import { toastController } from "@ionic/vue";
import { Field, Form } from "vee-validate";
import { z } from "zod";
import IconParkOutlineInfo from "~icons/icon-park-outline/info";
import { authClient, emailSchema } from "@/auth";
const emit = defineEmits<{
@@ -18,6 +19,7 @@ const canResend = computed(() => countdown.value === 0 && !isSending.value);
const email = ref("");
const emailError = ref("");
const agreeToTerms = ref(false);
let timer: NodeJS.Timeout | null = null;
@@ -85,6 +87,15 @@ async function sendOtp() {
}
function handleSubmit(values: GenericObject) {
if (!agreeToTerms.value) {
toastController.create({
message: "请同意服务条款后继续",
duration: 1000,
position: "top",
color: "warning",
}).then(toast => toast.present());
return;
}
emit("submit", values as EmailVerifyClient);
}
@@ -152,9 +163,28 @@ onUnmounted(() => {
>
{{ t('auth.login.loginButton') }}
</ion-button>
<ion-checkbox v-model="agreeToTerms" label-placement="end" class="mt-8 text-sm">
<span>我已经阅读并同意</span>
<a href="/auth/term" class="text-primary underline mx-2 underline-offset-3">
服务条款
</a>
</ion-checkbox>
<div class="text-sm text-text-300 mt-1 flex items-center">
<IconParkOutlineInfo class="inline-block mr-1" />
未注册时自动注册并登陆
</div>
</Form>
</template>
<style lang='css' scoped>
@reference "tailwindcss";
ion-checkbox {
--size: 18px;
}
ion-checkbox::part(label) {
margin-left: 4px;
}
</style>

View File

@@ -7,6 +7,7 @@ import { toTypedSchema } from "@vee-validate/zod";
import { chevronDown } from "ionicons/icons";
import { Field, Form } from "vee-validate";
import { z } from "zod";
import IconParkOutlineInfo from "~icons/icon-park-outline/info";
import { authClient, countries } from "@/auth";
import Country from "./country.vue";
@@ -28,6 +29,7 @@ const currentCountry = computed<PhoneCountry>(() => {
return countries.find(c => c.code === countryCode.value) || countries[0];
});
let timer: NodeJS.Timeout | null = null;
const agreeToTerms = ref(false);
function dismiss() {
modalInst.value?.$el.dismiss();
@@ -116,6 +118,15 @@ async function sendOtp() {
}
function handleSubmit(values: GenericObject) {
if (!agreeToTerms.value) {
toastController.create({
message: "请同意服务条款后继续",
duration: 1000,
position: "top",
color: "warning",
}).then(toast => toast.present());
return;
}
emit("submit", {
phoneNumber: `${currentCountry.value.dialCode}${values.phoneNumber}`,
code: values.code,
@@ -195,6 +206,18 @@ onUnmounted(() => {
>
{{ t('auth.login.loginButton') }}
</ion-button>
<ion-checkbox v-model="agreeToTerms" label-placement="end" class="mt-8 text-sm">
<span>我已经阅读并同意</span>
<a href="/auth/term" class="text-primary underline mx-2 underline-offset-3">
服务条款
</a>
</ion-checkbox>
<div class="text-sm text-text-300 mt-1 flex items-center">
<IconParkOutlineInfo class="inline-block mr-1" />
未注册时自动注册并登陆
</div>
</Form>
<ion-modal ref="modalInst" trigger="open-country" :initial-breakpoint="0.95" :breakpoints="[0, 0.95]">
@@ -206,4 +229,11 @@ onUnmounted(() => {
<style lang='css' scoped>
@reference "tailwindcss";
ion-checkbox {
--size: 18px;
}
ion-checkbox::part(label) {
margin-left: 4px;
}
</style>

View File

@@ -48,9 +48,9 @@ function onClose() {
<ion-icon slot="icon-only" :icon="closeOutline" />
</ion-button>
</ion-buttons>
<ion-button slot="end" fill="clear" @click="router.push('/auth/signup')">
<!-- <ion-button slot="end" fill="clear" @click="router.push('/auth/signup')">
{{ t('auth.login.signupButton') }}
</ion-button>
</ion-button> -->
</IonToolbar>
</IonHeader>
<IonContent :fullscreen="true" class="ion-padding">

97
src/views/auth/term.vue Normal file
View File

@@ -0,0 +1,97 @@
<script lang='ts' setup>
const { t } = useI18n();
const sections = [
"serviceDescription",
"userResponsibilities",
"accountSecurity",
"prohibitedActivities",
"riskDisclosure",
"privacyPolicy",
"intellectualProperty",
"disclaimers",
"termination",
"changes",
"governingLaw",
];
function getSectionParagraphs(section: string) {
const paragraphs: string[] = [];
let index = 0;
while (true) {
const key = `term.sections.${section}.content.${index}`;
const text = t(key);
if (text === key)
break;
paragraphs.push(text);
index++;
}
return paragraphs;
}
</script>
<template>
<ion-page>
<ion-header>
<ion-toolbar class="ion-toolbar">
<ion-buttons slot="start">
<ui-back-button default-href="/" />
</ion-buttons>
<ion-title>{{ t('term.title') }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true" class="ion-padding">
<div class="max-w-4xl mx-auto">
<!-- 最后更新时间 -->
<div class="mb-6 text-sm text-(--ion-color-medium)">
{{ t('term.lastUpdate') }}
</div>
<!-- 引言 -->
<section class="mb-8">
<p class="text-base leading-relaxed">
{{ t('term.introduction') }}
</p>
</section>
<!-- 服务条款内容 -->
<section v-for="(section, index) in sections" :key="index" class="mb-8">
<h2 class="text-lg font-semibold mb-4">
{{ index + 1 }}. {{ t(`term.sections.${section}.title`) }}
</h2>
<div class="space-y-3">
<p
v-for="(paragraph, pIndex) in getSectionParagraphs(section)"
:key="pIndex"
class="text-sm leading-relaxed text-(--ion-color-step-600)"
>
{{ paragraph }}
</p>
</div>
</section>
<!-- 联系信息 -->
<section class="mt-12 pt-6 border-t border-(--ion-color-step-150)">
<h2 class="text-lg font-semibold mb-4">
{{ t('term.contact.title') }}
</h2>
<p class="text-sm leading-relaxed text-(--ion-color-step-600)">
{{ t('term.contact.description') }}
</p>
<div class="mt-4 space-y-2">
<div class="text-sm">
<span class="text-(--ion-color-medium)">{{ t('term.contact.email') }}:</span>
<a href="mailto:support@example.com" class="ml-2 text-(--ion-color-primary)">
support@example.com
</a>
</div>
</div>
</section>
</div>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
</style>

View File

@@ -49,9 +49,9 @@ export default defineConfig({
type: "module",
},
manifest: {
name: "Riwsan",
short_name: "Riwsan",
description: "Riwsan Ionic App",
name: "Riwsan 瑞讯",
short_name: "Riwsan 瑞讯",
description: "Riwsan - 下一代数字资产交易平台",
theme_color: "#ffffff",
background_color: "#ffffff",
display: "standalone",