Prechádzať zdrojové kódy

tiktok一键登录、服务条款、隐私政策

urbanu 1 mesiac pred
rodič
commit
545356fe84

+ 12 - 5
index.html

@@ -21,12 +21,19 @@
     var state = urlParams.get('state');
 
     if (code) {
-      // 保存 Zalo 回调的 code 到 sessionStorage
-      sessionStorage.setItem('zalo_oauth_code', code);
-      if (state) {
-        sessionStorage.setItem('zalo_oauth_state', state);
+      // 判断是 TikTok 还是 Zalo 回调(TikTok 的 state 以 tiktok_ 开头)
+      if (state && state.indexOf('tiktok_') === 0) {
+        sessionStorage.setItem('tiktok_oauth_code', code);
+        sessionStorage.setItem('tiktok_oauth_state', state);
+        console.log('TikTok OAuth code saved:', code);
+      } else {
+        // 保存 Zalo 回调的 code 到 sessionStorage
+        sessionStorage.setItem('zalo_oauth_code', code);
+        if (state) {
+          sessionStorage.setItem('zalo_oauth_state', state);
+        }
+        console.log('Zalo OAuth code saved:', code);
       }
-      console.log('Zalo OAuth code saved:', code);
 
       // 重定向到 hash 路由的登录页,去掉 URL 中的参数
       window.location.href = window.location.origin + '/#/login';

+ 2 - 2
src/api/auth.ts

@@ -99,7 +99,7 @@ export function requestGetAuthUrl(redirectUri: string): Promise<CommonResponse<{
 
 // OAuth登录参数
 interface OAuthLoginParams {
-  provider: 'google' | 'zalo' | 'telegram';
+  provider: 'google' | 'zalo' | 'telegram' | 'tiktok';
   openId: string;
   nickname?: string;
   avatar?: string;
@@ -118,7 +118,7 @@ export function requestOAuthLogin(data: OAuthLoginParams): Promise<CommonRespons
 }
 
 // 获取OAuth配置
-export function requestOAuthConfig(): Promise<CommonResponse<{ googleClientId?: string; zaloAppId?: string; telegramBotName?: string; telegramBotId?: string }>> {
+export function requestOAuthConfig(): Promise<CommonResponse<{ googleClientId?: string; zaloAppId?: string; telegramBotName?: string; telegramBotId?: string; tiktokClientKey?: string }>> {
   return http.request({
     url: "/api/v1/dt/config/oauth",
     method: "get"

+ 50 - 0
src/locales/en.json

@@ -70,8 +70,58 @@
     "telegramNotConfigured": "Telegram login not configured",
     "zaloLoginFailed": "Zalo login failed",
     "zaloNotConfigured": "Zalo login not configured",
+    "tiktokLogin": "Sign in with TikTok",
+    "tiktokLoginFailed": "TikTok login failed",
+    "tiktokNotConfigured": "TikTok login not configured",
     "logoutConfirm": "Are you sure you want to log out?"
   },
+  "terms": {
+    "title": "Terms of Service",
+    "heading": "General Terms and Conditions",
+    "lastUpdated": "Last updated: December 1, 2025",
+    "section1Title": "1. Acceptance of Terms",
+    "section1Content": "By accessing and using vitiens.com (the \"Website\"), you agree to be bound by these Terms and Conditions and our Privacy Policy. If you do not agree, please stop using the Website immediately.",
+    "section2Title": "2. Website Usage",
+    "section2Intro": "Prohibited behavior: Users agree not to use the Website for any illegal purpose, including but not limited to:",
+    "section2Item1": "Spreading viruses or malicious code.",
+    "section2Item2": "Attempting unauthorized access to our servers.",
+    "section2Item3": "Scraping data without prior written consent.",
+    "section3Title": "3. Intellectual Property",
+    "section3Content": "All content on this Website (including text, graphics, logos, and software) is the property of Vitiens Group or its content suppliers and is protected by international copyright law. You may not reproduce, distribute, or create derivative works based on this content without explicit permission.",
+    "section4Title": "4. User Accounts",
+    "section4Intro": "If you create an account on this Website:",
+    "section4Item1": "You are responsible for maintaining the confidentiality of your login credentials.",
+    "section4Item2": "You are responsible for all activities that occur under your account.",
+    "section4Item3": "We reserve the right to terminate accounts that violate these terms.",
+    "section5Title": "5. Limitation of Liability",
+    "section5Content": "To the maximum extent permitted by law, Vitiens Group shall not be liable for any indirect, incidental, or consequential damages arising from your use of the Website. The Website is provided on an \"as is\" and \"as available\" basis without warranties of any kind.",
+    "section6Title": "6. External Links",
+    "section6Content": "This Website may contain links to third-party websites. We do not endorse or assume responsibility for the content, privacy policies, or practices of any third-party websites.",
+    "section7Title": "7. Changes to Terms",
+    "section7Content": "We reserve the right to modify these terms at any time. We will notify users of significant changes by posting a notice on the homepage or via email. Your continued use of the Website after changes are posted constitutes acceptance of the new terms."
+  },
+  "privacy": {
+    "title": "Privacy Policy",
+    "heading": "Privacy Policy",
+    "section1Title": "1. What Information Do We Collect?",
+    "section1P1": "Information you provide: We only collect your name and email address when you actively provide them (e.g., signing up for a newsletter or contacting us).",
+    "section1P2": "Automatically collected information: Our website collects some basic technical information, such as your IP address and the pages you click on. This helps us understand what works well and what needs improvement.",
+    "section2Title": "2. How Do We Use This Information?",
+    "section2Intro": "We use your information to:",
+    "section2Item1": "Send you updates or services you have requested.",
+    "section2Item2": "Make our website easier to use.",
+    "section2Item3": "Protect our website from spam or hacking.",
+    "section3Title": "3. Do We Share Your Data?",
+    "section3Content": "No, we do not sell your data. We only share your data with tools that keep the website running (such as our email service provider or website host). They must also keep your information confidential.",
+    "section4Title": "4. Your Choices",
+    "section4Item1": "Email: You can stop receiving emails by clicking the \"unsubscribe\" link in any email we send.",
+    "section4Item2": "Cookies: You can disable cookies in your browser settings, but some website features may not work properly after disabling them.",
+    "section4Item3": "Deletion: Want us to delete your information? Just send an email to parker1{'@'}kbcsvn.com and we will handle it for you.",
+    "section5Title": "5. Security",
+    "section5Content": "We do our best to keep your data secure, but remember that no website is completely secure. Please use strong passwords and be careful sharing your information online.",
+    "section6Title": "6. Changes",
+    "section6Content": "If we change how we handle your data, we will post an update on this page."
+  },
   "home": {
     "checkIn": "Check in",
     "welfareTask": "Welfare Task",

+ 50 - 0
src/locales/id.json

@@ -70,8 +70,58 @@
     "telegramNotConfigured": "Login Telegram tidak dikonfigurasi",
     "zaloLoginFailed": "Gagal masuk Zalo",
     "zaloNotConfigured": "Login Zalo tidak dikonfigurasi",
+    "tiktokLogin": "Masuk dengan TikTok",
+    "tiktokLoginFailed": "Gagal masuk TikTok",
+    "tiktokNotConfigured": "Login TikTok tidak dikonfigurasi",
     "logoutConfirm": "Apakah Anda yakin ingin keluar?"
   },
+  "terms": {
+    "title": "Ketentuan Layanan",
+    "heading": "Syarat dan Ketentuan Umum",
+    "lastUpdated": "Terakhir diperbarui: 1 Desember 2025",
+    "section1Title": "1. Penerimaan Ketentuan",
+    "section1Content": "Dengan mengakses dan menggunakan vitiens.com (\"Situs Web\"), Anda setuju untuk terikat oleh Syarat dan Ketentuan ini serta Kebijakan Privasi kami. Jika tidak setuju, harap segera berhenti menggunakan Situs Web.",
+    "section2Title": "2. Penggunaan Situs Web",
+    "section2Intro": "Perilaku yang dilarang: Pengguna setuju untuk tidak menggunakan Situs Web untuk tujuan ilegal, termasuk namun tidak terbatas pada:",
+    "section2Item1": "Menyebarkan virus atau kode berbahaya.",
+    "section2Item2": "Mencoba akses tidak sah ke server kami.",
+    "section2Item3": "Mengambil data tanpa persetujuan tertulis sebelumnya.",
+    "section3Title": "3. Kekayaan Intelektual",
+    "section3Content": "Semua konten di Situs Web ini (termasuk teks, grafik, logo, dan perangkat lunak) adalah milik Vitiens Group atau pemasok kontennya dan dilindungi oleh hukum hak cipta internasional. Anda tidak boleh memperbanyak, mendistribusikan, atau membuat karya turunan tanpa izin eksplisit.",
+    "section4Title": "4. Akun Pengguna",
+    "section4Intro": "Jika Anda membuat akun di Situs Web ini:",
+    "section4Item1": "Anda bertanggung jawab menjaga kerahasiaan kredensial login.",
+    "section4Item2": "Anda bertanggung jawab atas semua aktivitas di bawah akun Anda.",
+    "section4Item3": "Kami berhak menghentikan akun yang melanggar ketentuan ini.",
+    "section5Title": "5. Batasan Tanggung Jawab",
+    "section5Content": "Sejauh diizinkan oleh hukum, Vitiens Group tidak bertanggung jawab atas kerugian tidak langsung, insidental, atau konsekuensial yang timbul dari penggunaan Situs Web. Situs Web disediakan \"apa adanya\" dan \"sebagaimana tersedia\" tanpa jaminan apa pun.",
+    "section6Title": "6. Tautan Eksternal",
+    "section6Content": "Situs Web ini mungkin berisi tautan ke situs web pihak ketiga. Kami tidak mendukung atau bertanggung jawab atas konten, kebijakan privasi, atau praktik situs web pihak ketiga mana pun.",
+    "section7Title": "7. Perubahan Ketentuan",
+    "section7Content": "Kami berhak mengubah ketentuan ini kapan saja. Kami akan memberi tahu pengguna tentang perubahan signifikan dengan memposting pemberitahuan di halaman utama atau melalui email. Penggunaan Situs Web yang berkelanjutan setelah perubahan diposting merupakan penerimaan atas ketentuan baru."
+  },
+  "privacy": {
+    "title": "Kebijakan Privasi",
+    "heading": "Kebijakan Privasi",
+    "section1Title": "1. Informasi Apa yang Kami Kumpulkan?",
+    "section1P1": "Informasi yang Anda berikan: Kami hanya mengumpulkan nama dan alamat email Anda ketika Anda secara aktif memberikannya (misalnya, mendaftar buletin atau menghubungi kami).",
+    "section1P2": "Informasi yang dikumpulkan secara otomatis: Situs web kami mengumpulkan beberapa informasi teknis dasar, seperti alamat IP dan halaman yang Anda klik. Ini membantu kami memahami apa yang berfungsi dengan baik dan apa yang perlu diperbaiki.",
+    "section2Title": "2. Bagaimana Kami Menggunakan Informasi Ini?",
+    "section2Intro": "Kami menggunakan informasi Anda untuk:",
+    "section2Item1": "Mengirimkan pembaruan atau layanan yang Anda minta.",
+    "section2Item2": "Membuat situs web kami lebih mudah digunakan.",
+    "section2Item3": "Melindungi situs web kami dari spam atau peretasan.",
+    "section3Title": "3. Apakah Kami Membagikan Data Anda?",
+    "section3Content": "Tidak, kami tidak menjual data Anda. Kami hanya membagikan data dengan alat yang menjaga situs web tetap berjalan (seperti penyedia layanan email atau hosting). Mereka juga harus menjaga kerahasiaan informasi Anda.",
+    "section4Title": "4. Pilihan Anda",
+    "section4Item1": "Email: Anda dapat berhenti menerima email dengan mengklik tautan \"berhenti berlangganan\" di email mana pun yang kami kirim.",
+    "section4Item2": "Cookie: Anda dapat menonaktifkan Cookie di pengaturan browser, tetapi beberapa fitur mungkin tidak berfungsi dengan baik.",
+    "section4Item3": "Penghapusan: Ingin kami menghapus informasi Anda? Kirim email ke parker1{'@'}kbcsvn.com, kami akan menanganinya.",
+    "section5Title": "5. Keamanan",
+    "section5Content": "Kami berusaha sebaik mungkin untuk menjaga keamanan data Anda, tetapi ingat bahwa tidak ada situs web yang sepenuhnya aman. Gunakan kata sandi yang kuat dan berhati-hati saat membagikan informasi online.",
+    "section6Title": "6. Perubahan",
+    "section6Content": "Jika kami mengubah cara menangani data Anda, kami akan memposting pembaruan di halaman ini."
+  },
   "home": {
     "checkIn": "Absen",
     "welfareTask": "Tugas Bonus",

+ 50 - 0
src/locales/vi.json

@@ -70,8 +70,58 @@
     "telegramNotConfigured": "Đăng nhập Telegram chưa được cấu hình",
     "zaloLoginFailed": "Đăng nhập Zalo thất bại",
     "zaloNotConfigured": "Đăng nhập Zalo chưa được cấu hình",
+    "tiktokLogin": "Đăng nhập bằng TikTok",
+    "tiktokLoginFailed": "Đăng nhập TikTok thất bại",
+    "tiktokNotConfigured": "Đăng nhập TikTok chưa được cấu hình",
     "logoutConfirm": "Bạn có chắc chắn muốn đăng xuất?"
   },
+  "terms": {
+    "title": "Điều khoản dịch vụ",
+    "heading": "Điều khoản và Điều kiện chung",
+    "lastUpdated": "Cập nhật lần cuối: 1 tháng 12, 2025",
+    "section1Title": "1. Chấp nhận điều khoản",
+    "section1Content": "Truy cập và sử dụng vitiens.com (\"Trang web\") có nghĩa là bạn đồng ý tuân theo các Điều khoản và Điều kiện này cùng Chính sách bảo mật của chúng tôi. Nếu không đồng ý, vui lòng ngừng sử dụng Trang web ngay lập tức.",
+    "section2Title": "2. Sử dụng trang web",
+    "section2Intro": "Hành vi bị cấm: Người dùng đồng ý không sử dụng Trang web cho bất kỳ mục đích bất hợp pháp nào, bao gồm nhưng không giới hạn:",
+    "section2Item1": "Phát tán virus hoặc mã độc.",
+    "section2Item2": "Cố gắng truy cập trái phép vào máy chủ của chúng tôi.",
+    "section2Item3": "Thu thập dữ liệu mà không có sự đồng ý bằng văn bản trước.",
+    "section3Title": "3. Sở hữu trí tuệ",
+    "section3Content": "Tất cả nội dung trên Trang web này (bao gồm văn bản, đồ họa, logo và phần mềm) thuộc sở hữu của Vitiens Group hoặc nhà cung cấp nội dung và được bảo vệ bởi luật bản quyền quốc tế. Bạn không được sao chép, phân phối hoặc tạo tác phẩm phái sinh mà không có sự cho phép rõ ràng.",
+    "section4Title": "4. Tài khoản người dùng",
+    "section4Intro": "Nếu bạn tạo tài khoản trên Trang web này:",
+    "section4Item1": "Bạn chịu trách nhiệm bảo mật thông tin đăng nhập.",
+    "section4Item2": "Bạn chịu trách nhiệm về tất cả hoạt động dưới tài khoản của mình.",
+    "section4Item3": "Chúng tôi có quyền chấm dứt tài khoản vi phạm điều khoản.",
+    "section5Title": "5. Giới hạn trách nhiệm",
+    "section5Content": "Trong phạm vi tối đa được pháp luật cho phép, Vitiens Group không chịu trách nhiệm về bất kỳ thiệt hại gián tiếp, ngẫu nhiên hoặc hậu quả nào phát sinh từ việc sử dụng Trang web. Trang web được cung cấp trên cơ sở \"nguyên trạng\" và \"sẵn có\" mà không có bất kỳ bảo đảm nào.",
+    "section6Title": "6. Liên kết bên ngoài",
+    "section6Content": "Trang web này có thể chứa liên kết đến các trang web bên thứ ba. Chúng tôi không xác nhận hoặc chịu trách nhiệm về nội dung, chính sách bảo mật hoặc hoạt động của bất kỳ trang web bên thứ ba nào.",
+    "section7Title": "7. Thay đổi điều khoản",
+    "section7Content": "Chúng tôi có quyền sửa đổi các điều khoản này bất cứ lúc nào. Chúng tôi sẽ thông báo cho người dùng về những thay đổi quan trọng bằng cách đăng thông báo trên trang chủ hoặc qua email. Việc tiếp tục sử dụng Trang web sau khi thay đổi được đăng tải có nghĩa là bạn chấp nhận điều khoản mới."
+  },
+  "privacy": {
+    "title": "Chính sách bảo mật",
+    "heading": "Chính sách bảo mật",
+    "section1Title": "1. Chúng tôi thu thập thông tin gì?",
+    "section1P1": "Thông tin bạn cung cấp: Chúng tôi chỉ thu thập tên và địa chỉ email khi bạn chủ động cung cấp (ví dụ: đăng ký nhận bản tin hoặc liên hệ với chúng tôi).",
+    "section1P2": "Thông tin tự động thu thập: Trang web thu thập một số thông tin kỹ thuật cơ bản, như địa chỉ IP và các trang bạn truy cập. Điều này giúp chúng tôi hiểu tính năng nào hoạt động tốt và tính năng nào cần cải thiện.",
+    "section2Title": "2. Chúng tôi sử dụng thông tin này như thế nào?",
+    "section2Intro": "Chúng tôi sử dụng thông tin của bạn để:",
+    "section2Item1": "Gửi cho bạn các cập nhật hoặc dịch vụ bạn đã yêu cầu.",
+    "section2Item2": "Làm cho trang web dễ sử dụng hơn.",
+    "section2Item3": "Bảo vệ trang web khỏi spam hoặc tấn công.",
+    "section3Title": "3. Chúng tôi có chia sẻ dữ liệu không?",
+    "section3Content": "Không, chúng tôi không bán dữ liệu của bạn. Chúng tôi chỉ chia sẻ dữ liệu với các công cụ duy trì hoạt động trang web (như nhà cung cấp dịch vụ email hoặc hosting). Họ cũng phải giữ bí mật thông tin của bạn.",
+    "section4Title": "4. Lựa chọn của bạn",
+    "section4Item1": "Email: Bạn có thể ngừng nhận email bằng cách nhấp vào liên kết \"hủy đăng ký\" trong bất kỳ email nào chúng tôi gửi.",
+    "section4Item2": "Cookie: Bạn có thể tắt Cookie trong cài đặt trình duyệt, nhưng một số tính năng có thể không hoạt động bình thường.",
+    "section4Item3": "Xóa: Muốn chúng tôi xóa thông tin của bạn? Chỉ cần gửi email đến parker1{'@'}kbcsvn.com, chúng tôi sẽ xử lý.",
+    "section5Title": "5. Bảo mật",
+    "section5Content": "Chúng tôi cố gắng hết sức để bảo vệ dữ liệu của bạn, nhưng hãy nhớ rằng không có trang web nào hoàn toàn an toàn. Hãy sử dụng mật khẩu mạnh và cẩn thận khi chia sẻ thông tin trực tuyến.",
+    "section6Title": "6. Thay đổi",
+    "section6Content": "Nếu chúng tôi thay đổi cách xử lý dữ liệu, chúng tôi sẽ đăng cập nhật trên trang này."
+  },
   "home": {
     "checkIn": "Điểm danh",
     "welfareTask": "Nhiệm vụ phúc lợi",

+ 50 - 0
src/locales/zh.json

@@ -70,8 +70,58 @@
     "telegramNotConfigured": "Telegram 登录未配置",
     "zaloLoginFailed": "Zalo 登录失败",
     "zaloNotConfigured": "Zalo 登录未配置",
+    "tiktokLogin": "TikTok 登录",
+    "tiktokLoginFailed": "TikTok 登录失败",
+    "tiktokNotConfigured": "TikTok 登录未配置",
     "logoutConfirm": "确定要退出登录吗?"
   },
+  "terms": {
+    "title": "服务条款",
+    "heading": "通用条款及条件",
+    "lastUpdated": "最后更新日期:2025年12月1日",
+    "section1Title": "1. 接受条款",
+    "section1Content": "访问和使用 vitiens.com(\u201c本网站\u201d)即表示您同意受本条款及条件和我们的隐私政策的约束。如果您不同意,请立即停止使用本网站。",
+    "section2Title": "2. 网站使用",
+    "section2Intro": "禁止行为:用户同意不将本网站用于任何非法目的,包括但不限于:",
+    "section2Item1": "传播病毒或恶意代码。",
+    "section2Item2": "试图未经授权访问我们的服务器。",
+    "section2Item3": "未经事先书面同意抓取数据。",
+    "section3Title": "3. 知识产权",
+    "section3Content": "本网站上的所有内容(包括文本、图形、徽标和软件)均为 Vitiens Group 或其内容供应商的财产,并受国际版权法保护。未经明确许可,您不得复制、分发或基于这些内容创作衍生作品。",
+    "section4Title": "4. 用户账户",
+    "section4Intro": "如果您在本网站创建账户:",
+    "section4Item1": "您有责任维护您的登录凭证的机密性。",
+    "section4Item2": "您须对您账户下发生的所有活动负责。",
+    "section4Item3": "我们保留终止违反本条款的账户的权利。",
+    "section5Title": "5. 责任限制",
+    "section5Content": "在法律允许的最大范围内,Vitiens Group 对您使用本网站而引起的任何间接、附带或后果性损害概不负责。本网站按\u201c现状\u201d和\u201c现有\u201d基础提供,不提供任何形式的保证。",
+    "section6Title": "6. 外部链接",
+    "section6Content": "本网站可能包含指向第三方网站的链接。我们不认可或承担任何第三方网站的内容、隐私政策或做法的责任。",
+    "section7Title": "7. 条款变更",
+    "section7Content": "我们保留随时修改本条款的权利。我们将通过在首页发布通知或通过电子邮件告知用户重大变更。您在变更发布后继续使用本网站即表示您接受新条款。"
+  },
+  "privacy": {
+    "title": "隐私政策",
+    "heading": "隐私政策",
+    "section1Title": "1. 我们收集哪些信息?",
+    "section1P1": "您提供的信息:我们仅在您主动提供的情况下收集您的姓名和电子邮件地址(例如,注册新闻简报或联系我们)。",
+    "section1P2": "自动收集的信息:我们的网站会收集一些基本技术信息,例如您的 IP 地址以及您点击的页面。这有助于我们了解哪些功能运行良好,哪些功能存在问题。",
+    "section2Title": "2. 我们如何使用这些信息?",
+    "section2Intro": "我们使用您的信息来:",
+    "section2Item1": "向您发送您请求的更新或服务。",
+    "section2Item2": "使我们的网站更易于使用。",
+    "section2Item3": "保护我们的网站免受垃圾邮件或黑客攻击。",
+    "section3Title": "3. 我们会分享您的数据吗?",
+    "section3Content": "不会,我们不会出售您的数据。我们仅与维持网站运行的工具(例如我们的电子邮件服务提供商或网站托管商)共享您的数据。他们也必须对您的信息保密。",
+    "section4Title": "4. 您的选择",
+    "section4Item1": "电子邮件:您可以点击我们发送的任何电子邮件中的\u201c取消订阅\u201d链接来停止接收邮件。",
+    "section4Item2": "Cookie:您可以在浏览器设置中禁用 Cookie,但禁用后网站的某些功能可能无法正常使用。",
+    "section4Item3": "删除:想要我们删除您的信息?只需发送电子邮件至 parker1{'@'}kbcsvn.com,我们将为您处理。",
+    "section5Title": "5. 安全",
+    "section5Content": "我们会尽最大努力保护您的数据安全,但请记住,没有任何网站是绝对安全的。请使用强密码,并谨慎分享您的在线信息。",
+    "section6Title": "6. 变更",
+    "section6Content": "如果我们更改处理您数据的方式,我们会在此页面发布更新。"
+  },
   "home": {
     "checkIn": "签到",
     "welfareTask": "福利任务",

+ 26 - 0
src/router/index.js

@@ -32,6 +32,8 @@ const loadPassword = () => import("../views/profile/password.vue");
 const loadPaymentAccount = () => import("../views/profile/payment-account.vue");
 const loadContact = () => import("../views/contact/index.vue");
 const loadAbout = () => import("../views/about/index.vue");
+const loadTerms = () => import("../views/terms/index.vue");
+const loadPrivacy = () => import("../views/privacy/index.vue");
 
 const router = createRouter({
   history: createWebHashHistory(),
@@ -225,12 +227,36 @@ const router = createRouter({
           path: "about",
           name: "about",
           component: loadAbout
+        },
+        {
+          path: "terms",
+          name: "terms",
+          component: loadTerms
+        },
+        {
+          path: "privacy",
+          name: "privacy",
+          component: loadPrivacy
         }
       ]
     }
   ]
 });
 
+// 处理 OAuth 回调(TikTok 等第三方登录回调会将 code 放在 URL query 中)
+// 由于使用 hash 路由,回调 URL 不带 hash,需要在路由初始化前处理
+const urlParams = new URLSearchParams(window.location.search);
+const oauthCode = urlParams.get('code');
+const oauthState = urlParams.get('state');
+if (oauthCode && oauthState) {
+  if (oauthState.startsWith('tiktok_')) {
+    sessionStorage.setItem('tiktok_oauth_code', oauthCode);
+    sessionStorage.setItem('tiktok_oauth_state', oauthState);
+  }
+  // 清除 URL 参数并跳转到登录页
+  window.location.href = window.location.origin + window.location.pathname + '#/login';
+}
+
 // 路由守卫
 router.beforeEach((to, from, next) => {
   const token = localStorage.getItem("token");

+ 88 - 2
src/views/login/index.vue

@@ -155,6 +155,16 @@
           </svg>
           <span>{{ $t('auth.zaloLogin') || '使用 Zalo 登录' }}</span>
         </div>
+
+        <!-- TikTok 登录按钮 -->
+        <div class="oauth-btn tiktok-btn" :class="{ loading: tiktokLoading }" @click="handleTiktokLogin" v-if="tiktokClientKey">
+          <svg class="tiktok-icon" viewBox="0 0 24 24" width="20" height="20">
+            <path fill="#25F4EE" d="M16.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 15.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
+            <path fill="#FE2C55" d="M17.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 16.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
+          </svg>
+          <span>{{ $t('auth.tiktokLogin') || 'TikTok 登录' }}</span>
+          <van-loading v-if="tiktokLoading" size="16px" color="#fff" />
+        </div>
       </div>
 
       <!-- 注册入口 -->
@@ -193,6 +203,8 @@ const telegramBotName = ref('');
 const telegramBotId = ref('');
 const zaloLoading = ref(false);
 const zaloAppId = ref('');
+const tiktokLoading = ref(false);
+const tiktokClientKey = ref('');
 
 const loginType = ref<'phone' | 'email'>('phone');
 const showPassword = ref(false);
@@ -321,6 +333,12 @@ const initOAuth = async () => {
         zaloAppId.value = configRes.data.zaloAppId;
         initZaloLogin();
       }
+
+      // 初始化 TikTok Login
+      if (configRes.data.tiktokClientKey) {
+        tiktokClientKey.value = configRes.data.tiktokClientKey;
+        initTiktokLogin();
+      }
     }
   } catch (error) {
     console.warn('Failed to load OAuth config:', error);
@@ -507,8 +525,9 @@ const onGoogleMessage = async (event: MessageEvent) => {
 // 组件挂载时初始化 OAuth
 onMounted(() => {
   window.addEventListener('message', onGoogleMessage);
-  // 先检查 Zalo 回调
+  // 先检查 Zalo/TikTok 回调
   initZaloLogin();
+  initTiktokLogin();
   // 再初始化 OAuth 配置
   initOAuth();
 });
@@ -627,6 +646,64 @@ const handleZaloLogin = async () => {
   }
 };
 
+// 初始化 TikTok 登录(检查回调参数)
+const initTiktokLogin = () => {
+  // 路由守卫已将 TikTok 回调参数从 URL 存入 sessionStorage
+  const code = sessionStorage.getItem('tiktok_oauth_code');
+  const redirectUri = sessionStorage.getItem('tiktok_redirect_uri');
+
+  if (code && redirectUri) {
+    sessionStorage.removeItem('tiktok_oauth_code');
+    sessionStorage.removeItem('tiktok_oauth_state');
+    sessionStorage.removeItem('tiktok_redirect_uri');
+    handleTiktokCallback(code, redirectUri);
+  }
+};
+
+// TikTok 登录回调处理
+const handleTiktokCallback = async (code: string, redirectUri: string) => {
+  tiktokLoading.value = true;
+  try {
+    const res = await requestOAuthLogin({
+      provider: 'tiktok',
+      openId: code,
+      nickname: '',
+      avatar: '',
+      extra: redirectUri
+    });
+
+    if (res.code === 200) {
+      localStorage.setItem('token', res.data.token);
+      userStore.setUserInfo(res.data.user);
+      toast.success(t('auth.loginSuccess'));
+      router.replace('/home');
+    } else {
+      toast.error(res.msg || t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
+    }
+  } catch (error) {
+    console.error('TikTok login error:', error);
+    toast.error(t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
+  } finally {
+    tiktokLoading.value = false;
+  }
+};
+
+// 触发 TikTok 登录
+const handleTiktokLogin = () => {
+  if (!tiktokClientKey.value) {
+    toast.error(t('auth.tiktokNotConfigured') || 'TikTok 登录未配置');
+    return;
+  }
+
+  const redirectUri = window.location.origin + '/login';
+  sessionStorage.setItem('tiktok_redirect_uri', redirectUri);
+  const state = 'tiktok_' + generateRandomString(16);
+
+  const authUrl = `https://www.tiktok.com/v2/auth/authorize/?client_key=${tiktokClientKey.value}&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
+
+  window.location.href = authUrl;
+};
+
 const goForgotPassword = () => {
   router.push('/forgot-password');
 };
@@ -805,7 +882,7 @@ const goRegister = () => {
       opacity: 0.7;
     }
 
-    .google-icon, .zalo-icon, .telegram-icon {
+    .google-icon, .zalo-icon, .telegram-icon, .tiktok-icon {
       flex-shrink: 0;
     }
 
@@ -836,6 +913,15 @@ const goRegister = () => {
         background: #0077b5;
       }
     }
+
+    &.tiktok-btn {
+      background: #000;
+      color: #fff;
+
+      &:active {
+        background: #333;
+      }
+    }
   }
 }
 

+ 131 - 0
src/views/privacy/index.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="privacy-page">
+    <div class="header">
+      <van-icon name="arrow-left" @click="goBack" />
+      <span>{{ $t('privacy.title') }}</span>
+      <span></span>
+    </div>
+
+    <div class="content">
+      <div class="section">
+        <h2>{{ $t('privacy.heading') }}</h2>
+      </div>
+
+      <!-- 1. 我们收集哪些信息(两段文字) -->
+      <div class="section">
+        <h3>{{ $t('privacy.section1Title') }}</h3>
+        <p>{{ $t('privacy.section1P1') }}</p>
+        <p>{{ $t('privacy.section1P2') }}</p>
+      </div>
+
+      <!-- 2. 如何使用信息(含列表) -->
+      <div class="section">
+        <h3>{{ $t('privacy.section2Title') }}</h3>
+        <p>{{ $t('privacy.section2Intro') }}</p>
+        <ul>
+          <li>{{ $t('privacy.section2Item1') }}</li>
+          <li>{{ $t('privacy.section2Item2') }}</li>
+          <li>{{ $t('privacy.section2Item3') }}</li>
+        </ul>
+      </div>
+
+      <!-- 3. 是否分享数据 -->
+      <div class="section">
+        <h3>{{ $t('privacy.section3Title') }}</h3>
+        <p>{{ $t('privacy.section3Content') }}</p>
+      </div>
+
+      <!-- 4. 您的选择(含列表) -->
+      <div class="section">
+        <h3>{{ $t('privacy.section4Title') }}</h3>
+        <ul>
+          <li>{{ $t('privacy.section4Item1') }}</li>
+          <li>{{ $t('privacy.section4Item2') }}</li>
+          <li>{{ $t('privacy.section4Item3') }}</li>
+        </ul>
+      </div>
+
+      <!-- 5. 安全 -->
+      <div class="section">
+        <h3>{{ $t('privacy.section5Title') }}</h3>
+        <p>{{ $t('privacy.section5Content') }}</p>
+      </div>
+
+      <!-- 6. 变更 -->
+      <div class="section">
+        <h3>{{ $t('privacy.section6Title') }}</h3>
+        <p>{{ $t('privacy.section6Content') }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+const goBack = () => {
+  router.back();
+};
+</script>
+
+<style lang="scss" scoped>
+.privacy-page {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
+  color: #fff;
+}
+
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  font-size: 18px;
+  font-weight: 500;
+
+  .van-icon {
+    font-size: 20px;
+    cursor: pointer;
+  }
+}
+
+.content {
+  padding: 0 20px 40px;
+
+  .section {
+    margin-bottom: 20px;
+
+    h2 {
+      font-size: 20px;
+      margin-bottom: 16px;
+      color: #ffc300;
+    }
+
+    h3 {
+      font-size: 16px;
+      margin-bottom: 8px;
+      color: rgba(255, 255, 255, 0.9);
+    }
+
+    p {
+      font-size: 14px;
+      line-height: 1.8;
+      color: rgba(255, 255, 255, 0.7);
+      margin-bottom: 8px;
+    }
+
+    ul {
+      margin: 8px 0 0 20px;
+      padding: 0;
+
+      li {
+        font-size: 14px;
+        line-height: 1.8;
+        color: rgba(255, 255, 255, 0.7);
+        margin-bottom: 4px;
+      }
+    }
+  }
+}
+</style>

+ 99 - 3
src/views/register/index.vue

@@ -236,6 +236,16 @@
           <span>{{ $t('auth.telegramLogin') || 'Telegram 登录' }}</span>
         </div>
 
+        <!-- TikTok 登录按钮 -->
+        <div class="oauth-btn tiktok-btn" :class="{ loading: tiktokLoading }" @click="handleTiktokLogin" v-if="tiktokClientKey">
+          <svg class="tiktok-icon" viewBox="0 0 24 24" width="20" height="20">
+            <path fill="#25F4EE" d="M16.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 15.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
+            <path fill="#FE2C55" d="M17.6 5.82s.51.5 0 0A4.278 4.278 0 0 1 16.54 3h-3.09v12.4a2.592 2.592 0 0 1-2.59 2.5c-1.42 0-2.6-1.16-2.6-2.6 0-1.72 1.66-3.01 3.37-2.48V9.66c-3.45-.46-6.47 2.22-6.47 5.64 0 3.33 2.76 5.7 5.69 5.7 3.14 0 5.69-2.55 5.69-5.7V9.01a7.35 7.35 0 0 0 4.3 1.38V7.3s-1.88.09-3.24-1.48z"/>
+          </svg>
+          <span>{{ $t('auth.tiktokLogin') || 'TikTok 登录' }}</span>
+          <van-loading v-if="tiktokLoading" size="16px" color="#fff" />
+        </div>
+
         <!-- Zalo 登录按钮 -->
         <div class="oauth-btn zalo-btn" @click="handleZaloLogin">
           <svg class="zalo-icon" viewBox="0 0 24 24" width="20" height="20">
@@ -280,6 +290,8 @@ const googleLoading = ref(false);
 const googleClientId = ref('');
 const telegramBotName = ref('');
 const telegramBotId = ref('');
+const tiktokLoading = ref(false);
+const tiktokClientKey = ref('');
 
 const registerType = ref<'phone' | 'email'>('phone');
 const showPassword = ref(false);
@@ -433,11 +445,11 @@ const goLogin = () => {
 };
 
 const showTerms = () => {
-  // TODO: 显示服务条款
+  router.push('/terms');
 };
 
 const showPrivacy = () => {
-  // TODO: 显示隐私政策
+  router.push('/privacy');
 };
 
 // 获取OAuth配置并初始化 Google Sign-In
@@ -469,6 +481,11 @@ const initGoogleSignIn = async () => {
         telegramBotName.value = configRes.data.telegramBotName;
         telegramBotId.value = configRes.data.telegramBotId || '';
       }
+
+      // 初始化 TikTok Login
+      if (configRes.data.tiktokClientKey) {
+        tiktokClientKey.value = configRes.data.tiktokClientKey;
+      }
     }
   } catch (error) {
     console.warn('Failed to load OAuth config:', error);
@@ -603,7 +620,77 @@ const handleTelegramCallback = async (user: any) => {
 };
 
 
+// 生成随机字符串
+const generateRandomString = (length: number): string => {
+  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+  let result = '';
+  for (let i = 0; i < length; i++) {
+    result += chars.charAt(Math.floor(Math.random() * chars.length));
+  }
+  return result;
+};
+
+// 初始化 TikTok 登录(检查回调参数)
+const initTiktokLogin = () => {
+  const code = sessionStorage.getItem('tiktok_oauth_code');
+  const redirectUri = sessionStorage.getItem('tiktok_redirect_uri');
+
+  if (code && redirectUri) {
+    sessionStorage.removeItem('tiktok_oauth_code');
+    sessionStorage.removeItem('tiktok_oauth_state');
+    sessionStorage.removeItem('tiktok_redirect_uri');
+    handleTiktokCallback(code, redirectUri);
+  }
+};
+
+// TikTok 登录回调处理
+const handleTiktokCallback = async (code: string, redirectUri: string) => {
+  tiktokLoading.value = true;
+  try {
+    const inviteCode = phoneForm.inviteCode || emailForm.inviteCode || (route.query.smid as string) || '';
+    const res = await requestOAuthLogin({
+      provider: 'tiktok',
+      openId: code,
+      nickname: '',
+      avatar: '',
+      extra: redirectUri,
+      inviteCode
+    });
+
+    if (res.code === 200) {
+      localStorage.setItem('token', res.data.token);
+      userStore.setUserInfo(res.data.user);
+      toast.success(t('auth.loginSuccess'));
+      router.replace('/home');
+    } else {
+      toast.error(res.msg || t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
+    }
+  } catch (error) {
+    console.error('TikTok login error:', error);
+    toast.error(t('auth.tiktokLoginFailed') || 'TikTok 登录失败');
+  } finally {
+    tiktokLoading.value = false;
+  }
+};
+
+// 触发 TikTok 登录
+const handleTiktokLogin = () => {
+  if (!tiktokClientKey.value) {
+    toast.error(t('auth.tiktokNotConfigured') || 'TikTok 登录未配置');
+    return;
+  }
+
+  const redirectUri = window.location.origin + '/login';
+  sessionStorage.setItem('tiktok_redirect_uri', redirectUri);
+  const state = 'tiktok_' + generateRandomString(16);
+
+  const authUrl = `https://www.tiktok.com/v2/auth/authorize/?client_key=${tiktokClientKey.value}&scope=user.info.basic&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
+
+  window.location.href = authUrl;
+};
+
 onMounted(() => {
+  initTiktokLogin();
   initGoogleSignIn();
 });
 </script>
@@ -786,7 +873,7 @@ onMounted(() => {
       opacity: 0.7;
     }
 
-    .google-icon, .zalo-icon {
+    .google-icon, .zalo-icon, .telegram-icon, .tiktok-icon {
       flex-shrink: 0;
     }
 
@@ -809,6 +896,15 @@ onMounted(() => {
       }
     }
 
+    &.tiktok-btn {
+      background: #000;
+      color: #fff;
+
+      &:active {
+        background: #333;
+      }
+    }
+
     &.zalo-btn {
       background: #0068FF;
       color: #fff;

+ 143 - 0
src/views/terms/index.vue

@@ -0,0 +1,143 @@
+<template>
+  <div class="terms-page">
+    <div class="header">
+      <van-icon name="arrow-left" @click="goBack" />
+      <span>{{ $t('terms.title') }}</span>
+      <span></span>
+    </div>
+
+    <div class="content">
+      <div class="section">
+        <h2>{{ $t('terms.heading') }}</h2>
+        <p class="last-updated">{{ $t('terms.lastUpdated') }}</p>
+      </div>
+
+      <!-- 1. 接受条款 -->
+      <div class="section">
+        <h3>{{ $t('terms.section1Title') }}</h3>
+        <p>{{ $t('terms.section1Content') }}</p>
+      </div>
+
+      <!-- 2. 网站使用(含列表) -->
+      <div class="section">
+        <h3>{{ $t('terms.section2Title') }}</h3>
+        <p>{{ $t('terms.section2Intro') }}</p>
+        <ul>
+          <li>{{ $t('terms.section2Item1') }}</li>
+          <li>{{ $t('terms.section2Item2') }}</li>
+          <li>{{ $t('terms.section2Item3') }}</li>
+        </ul>
+      </div>
+
+      <!-- 3. 知识产权 -->
+      <div class="section">
+        <h3>{{ $t('terms.section3Title') }}</h3>
+        <p>{{ $t('terms.section3Content') }}</p>
+      </div>
+
+      <!-- 4. 用户账户(含列表) -->
+      <div class="section">
+        <h3>{{ $t('terms.section4Title') }}</h3>
+        <p>{{ $t('terms.section4Intro') }}</p>
+        <ul>
+          <li>{{ $t('terms.section4Item1') }}</li>
+          <li>{{ $t('terms.section4Item2') }}</li>
+          <li>{{ $t('terms.section4Item3') }}</li>
+        </ul>
+      </div>
+
+      <!-- 5. 责任限制 -->
+      <div class="section">
+        <h3>{{ $t('terms.section5Title') }}</h3>
+        <p>{{ $t('terms.section5Content') }}</p>
+      </div>
+
+      <!-- 6. 外部链接 -->
+      <div class="section">
+        <h3>{{ $t('terms.section6Title') }}</h3>
+        <p>{{ $t('terms.section6Content') }}</p>
+      </div>
+
+      <!-- 7. 条款变更 -->
+      <div class="section">
+        <h3>{{ $t('terms.section7Title') }}</h3>
+        <p>{{ $t('terms.section7Content') }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+const goBack = () => {
+  router.back();
+};
+</script>
+
+<style lang="scss" scoped>
+.terms-page {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #1a1a2e 0%, #16213e 100%);
+  color: #fff;
+}
+
+.header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px 20px;
+  font-size: 18px;
+  font-weight: 500;
+
+  .van-icon {
+    font-size: 20px;
+    cursor: pointer;
+  }
+}
+
+.content {
+  padding: 0 20px 40px;
+
+  .section {
+    margin-bottom: 20px;
+
+    h2 {
+      font-size: 20px;
+      margin-bottom: 8px;
+      color: #ffc300;
+    }
+
+    .last-updated {
+      font-size: 13px;
+      color: rgba(255, 255, 255, 0.5);
+      margin-bottom: 16px;
+    }
+
+    h3 {
+      font-size: 16px;
+      margin-bottom: 8px;
+      color: rgba(255, 255, 255, 0.9);
+    }
+
+    p {
+      font-size: 14px;
+      line-height: 1.8;
+      color: rgba(255, 255, 255, 0.7);
+    }
+
+    ul {
+      margin: 8px 0 0 20px;
+      padding: 0;
+
+      li {
+        font-size: 14px;
+        line-height: 1.8;
+        color: rgba(255, 255, 255, 0.7);
+        margin-bottom: 4px;
+      }
+    }
+  }
+}
+</style>