接口调试
This commit is contained in:
@@ -7,7 +7,7 @@ import ArrowIcon from './arrow-icon2.svg?component'
|
||||
const faqList = ref([
|
||||
{
|
||||
id: 1,
|
||||
question: '登陆公共 Apple ID 时,出现“双重验证”提示,且要求输入验证码怎么办?',
|
||||
question: '登录公共 Apple ID 时,出现“双重验证”提示,且要求输入验证码怎么办?',
|
||||
answer:
|
||||
'这是由于有其他用户没有按照教程操作,将公共 Apple ID 绑定手机号码,开启了双重验证所导致。\n\n解决办法:请您先清除浏览器缓存,再刷新本页面,获取新的公共 Apple ID,接着请按照教程一步一步操作。',
|
||||
},
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<div v-show="activeIndex === 1">
|
||||
<div class="pt-[34px] pb-[15px] text-center font-[900]">
|
||||
登陆海外 Apple ID 后再点击下方下载按钮
|
||||
登录海外 Apple ID 后再点击下方下载按钮
|
||||
</div>
|
||||
</div>
|
||||
<!-- tab2 -->
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="visible"
|
||||
class="!pointer-events-auto fixed inset-0 z-[100] flex items-center justify-center p-4"
|
||||
>
|
||||
<div
|
||||
class="backdrop-blur-[2px z-[110]] absolute inset-0 bg-black/40"
|
||||
@click.stop="hide"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="relative z-10 w-full max-w-[280px] rounded-[40px] border border-white/10 bg-[#999999] p-8 shadow-2xl"
|
||||
@click.stop
|
||||
>
|
||||
<div class="space-y-2 text-black">
|
||||
<h3 class="flex items-start text-lg font-black"><span class="mr-1">*</span>重要提示</h3>
|
||||
<p class="text-[15px] leading-relaxed font-bold tracking-tight">
|
||||
验证邮件已发送至邮箱,如无法找到,请检查垃圾邮件箱或营销邮件箱。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
const timer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const show = () => {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
const hide = () => {
|
||||
console.log('hide')
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 组件卸载前清除计时器防止内存泄漏
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearTimeout(timer)
|
||||
})
|
||||
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</style>
|
||||
@@ -1,31 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
const email = ref('')
|
||||
const code = ref('')
|
||||
const countdown = ref(0)
|
||||
|
||||
const handleGetCode = () => {
|
||||
if (!email.value) {
|
||||
toast('请输入邮箱')
|
||||
return
|
||||
}
|
||||
// 模拟倒计时逻辑
|
||||
countdown.value = 60
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) clearInterval(timer)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
console.log('登录提交', { email: email.value, code: code.value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-6 text-black">
|
||||
<div class="overflow-hidden rounded-[20px] bg-[#78788029] px-4">
|
||||
@@ -62,6 +34,7 @@ const handleLogin = () => {
|
||||
<div class="flex gap-4 pt-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
@click="emit('close')"
|
||||
class="h-[48px] flex-1 cursor-pointer rounded-[25px] bg-[#D1D1D1] text-lg font-medium text-[#757575] hover:bg-[#C1C1C1]"
|
||||
>
|
||||
取消
|
||||
@@ -70,12 +43,44 @@ const handleLogin = () => {
|
||||
@click="handleLogin"
|
||||
class="h-[48px] flex-1 cursor-pointer rounded-[25px] bg-[#A8FF53] text-lg font-medium text-black hover:bg-[#96E64A]"
|
||||
>
|
||||
登陆/注册
|
||||
登录/注册
|
||||
</Button>
|
||||
</div>
|
||||
<CodeSentTip ref="CodeSentTipRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { toast } from 'vue-sonner'
|
||||
import CodeSentTip from '@/pages/Home/components/CodeSentTip.vue'
|
||||
const CodeSentTipRef = ref(null)
|
||||
const email = ref('')
|
||||
const code = ref('')
|
||||
const countdown = ref(0)
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
const handleGetCode = () => {
|
||||
if (!email.value) {
|
||||
toast('请输入邮箱')
|
||||
return
|
||||
}
|
||||
CodeSentTipRef.value.show()
|
||||
// 模拟倒计时逻辑
|
||||
countdown.value = 60
|
||||
const timer = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) clearInterval(timer)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
console.log('登录提交', { email: email.value, code: code.value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 移除 Shadcn Input 的默认边框阴影,保持纯净感 */
|
||||
input:focus {
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
:showCloseButton="false"
|
||||
>
|
||||
<DialogHeader class="py-2 pl-2">
|
||||
<DialogTitle class="text-left">登陆Hi快帐户</DialogTitle>
|
||||
<DialogTitle class="text-left">登录Hi快帐户</DialogTitle>
|
||||
<DialogDescription class="text-left text-base text-black">
|
||||
如使用新邮箱地址,将自动为您创建Hi快帐户
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<LoginForm />
|
||||
<LoginForm @close="isOpen = false" />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from '@/components/ui/dialog'
|
||||
import LoginForm from './LoginForm.vue'
|
||||
|
||||
const isOpen = ref(true)
|
||||
const isOpen = ref(false)
|
||||
|
||||
const setOpen = (value: boolean) => {
|
||||
isOpen.value = value
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
@click="openLoginModal"
|
||||
class="flex h-[48px] items-center rounded-full bg-[#70C877] px-6 text-sm font-bold backdrop-blur-md transition hover:bg-white/30"
|
||||
>
|
||||
登陆 / 注册
|
||||
登录 / 注册
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="md:w-1/2">
|
||||
<div class="mb-[20px] ml-[42px]">
|
||||
<h2 class="mb-2 text-5xl font-black italic md:text-7xl">
|
||||
<img :src="Logo" alt="Hi快VPN" class="h-[34px] w-auto" />
|
||||
<Logo />
|
||||
</h2>
|
||||
<p class="font-600 text-3xl md:text-4xl">网在我在, 网快我快</p>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import PlanCard from '@/components/user-center/PlanCard.vue'
|
||||
import DeviceList from '@/components/user-center/DeviceList.vue'
|
||||
import PaymentMethod from '@/components/user-center/PaymentMethod.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
// --- Mock Data ---
|
||||
const devices = [
|
||||
{ id: '1', name: 'XXX的 MacBook Pro 15-inch', type: 'desktop' as const, deviceId: '87654321' },
|
||||
{ id: '2', name: 'XXX的 iPhone 15 Pro Max', type: 'mobile' as const, deviceId: '12345678' },
|
||||
]
|
||||
|
||||
const plans = [
|
||||
{ id: 'p1', days: 7, price: 2.79, dailyPrice: '0.39' },
|
||||
{ id: 'p2', days: 30, price: 5.99, dailyPrice: '0.19', discount: '50% off' },
|
||||
{ id: 'p3', days: 90, price: 12.99, dailyPrice: '0.14', discount: '64% off' },
|
||||
{ id: 'p4', days: 365, price: 44.99, dailyPrice: '0.12', discount: '70% off', inverted: true },
|
||||
]
|
||||
|
||||
// --- State ---
|
||||
const selectedPlanId = ref('p2')
|
||||
const selectedPayment = ref('alipay')
|
||||
|
||||
// --- Handlers ---
|
||||
const handlePlanSelect = (id: string) => {
|
||||
selectedPlanId.value = id
|
||||
}
|
||||
const currentPlanIndex = ref(3)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Main Neon Green Card -->
|
||||
<div class="pt-[35px]">
|
||||
@@ -67,6 +37,37 @@ const currentPlanIndex = ref(3)
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import PlanCard from '@/components/user-center/PlanCard.vue'
|
||||
import DeviceList from '@/components/user-center/DeviceList.vue'
|
||||
import PaymentMethod from '@/components/user-center/PaymentMethod.vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import request from '@/utils/request'
|
||||
// --- Mock Data ---
|
||||
const devices = [
|
||||
{ id: '1', name: 'XXX的 MacBook Pro 15-inch', type: 'desktop' as const, deviceId: '87654321' },
|
||||
{ id: '2', name: 'XXX的 iPhone 15 Pro Max', type: 'mobile' as const, deviceId: '12345678' },
|
||||
]
|
||||
|
||||
const plans = [
|
||||
{ id: 'p1', days: 7, price: 2.79, dailyPrice: '0.39' },
|
||||
{ id: 'p2', days: 30, price: 5.99, dailyPrice: '0.19', discount: '50% off' },
|
||||
{ id: 'p3', days: 90, price: 12.99, dailyPrice: '0.14', discount: '64% off' },
|
||||
{ id: 'p4', days: 365, price: 44.99, dailyPrice: '0.12', discount: '70% off', inverted: true },
|
||||
]
|
||||
|
||||
// --- State ---
|
||||
const selectedPlanId = ref('p2')
|
||||
const selectedPayment = ref('alipay')
|
||||
|
||||
// --- Handlers ---
|
||||
const handlePlanSelect = (id: string) => {
|
||||
selectedPlanId.value = id
|
||||
}
|
||||
const currentPlanIndex = ref(3)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Simplified layout font */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&display=swap');
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import MobileLayout from './MobileLayout/index.vue'
|
||||
import Logo from '@/pages/Home/logo.svg?component'
|
||||
import MobileLogo from '@/pages/Home/mobile-logo.svg?component'
|
||||
|
||||
// --- State ---
|
||||
const selectedPlanId = ref('p2')
|
||||
const selectedPayment = ref('alipay')
|
||||
|
||||
// --- Handlers ---
|
||||
const handlePlanSelect = (id: string) => {
|
||||
selectedPlanId.value = id
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-black text-black">
|
||||
<!-- Full Width Header -->
|
||||
@@ -41,6 +25,44 @@ const handlePlanSelect = (id: string) => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import MobileLayout from './MobileLayout/index.vue'
|
||||
import Logo from '@/pages/Home/logo.svg?component'
|
||||
import MobileLogo from '@/pages/Home/mobile-logo.svg?component'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// --- State ---
|
||||
const selectedPlanId = ref('p2')
|
||||
const selectedPayment = ref('alipay')
|
||||
|
||||
// --- Handlers ---
|
||||
const handlePlanSelect = (id: string) => {
|
||||
selectedPlanId.value = id
|
||||
}
|
||||
|
||||
const devices = ref([])
|
||||
function init() {
|
||||
// 设备列表
|
||||
request.get('/public/user/devices').then((res) => {
|
||||
devices.value = res.list
|
||||
})
|
||||
// 订阅详情
|
||||
request.get('/public/subscribe/list').then((res) => {
|
||||
console.log(222, res.list)
|
||||
})
|
||||
// 获取支付方式
|
||||
request.get('/public/payment/methods').then((res) => {
|
||||
console.log(33, res)
|
||||
})
|
||||
// 订阅列表
|
||||
request.get('/public/subscribe/list').then((res) => {
|
||||
console.log(44, res)
|
||||
})
|
||||
}
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Simplified layout font */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&display=swap');
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
|
||||
/**
|
||||
* HiAesUtil - AES-256-CBC 加密工具类
|
||||
* 逻辑与 Dart 版本保持 100% 同步
|
||||
*/
|
||||
export class HiAesUtil {
|
||||
/**
|
||||
* 生成 32 字节密钥 (SHA-256)
|
||||
*/
|
||||
static _generateKey(keyStr) {
|
||||
return CryptoJS.SHA256(keyStr)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 16 字节 IV
|
||||
* 逻辑: SHA-256(MD5(nonce) + keyStr) -> 取前16字节
|
||||
*/
|
||||
static _generateIv(nonce, keyStr) {
|
||||
// 1. MD5 处理 nonce
|
||||
const md5Hash = CryptoJS.MD5(nonce)
|
||||
// 2. 转为 16 进制字符串
|
||||
const md5Hex = md5Hash.toString(CryptoJS.enc.Hex)
|
||||
// 3. 拼接 md5Hex + keyStr 并做 SHA-256
|
||||
const finalHash = CryptoJS.SHA256(md5Hex + keyStr)
|
||||
|
||||
// 4. 重要:截取前 16 字节 (128位)
|
||||
// CryptoJS 的 WordArray 由 32 位整数组成,16 字节即前 4 个 words
|
||||
const iv = CryptoJS.lib.WordArray.create(finalHash.words.slice(0, 4), 16)
|
||||
return iv
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
* @param {string} plainText - 明文
|
||||
* @param {string} keyStr - 密钥
|
||||
*/
|
||||
static encryptData(plainText, keyStr) {
|
||||
// 生成 ISO8601 时间戳 (与 Dart DateTime.now().toIso8601String() 对应)
|
||||
const nonce = new Date().toISOString()
|
||||
|
||||
const key = this._generateKey(keyStr)
|
||||
const iv = this._generateIv(nonce, keyStr)
|
||||
|
||||
const encrypted = CryptoJS.AES.encrypt(plainText, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
})
|
||||
|
||||
return {
|
||||
data: encrypted.toString(), // Base64 字符串
|
||||
time: nonce,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
* @param {string} encryptedData - Base64 加密字符串
|
||||
* @param {string} nonce - 时间戳
|
||||
* @param {string} keyStr - 密钥
|
||||
*/
|
||||
static decryptData(encryptedData, nonce, keyStr) {
|
||||
const key = this._generateKey(keyStr)
|
||||
const iv = this._generateIv(nonce, keyStr)
|
||||
|
||||
const decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
})
|
||||
|
||||
return decrypted.toString(CryptoJS.enc.Utf8)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user