feat: 添加安全中心功能,支持更改登录密码和交易密码,集成表单验证和状态管理
This commit is contained in:
14
pnpm-lock.yaml
generated
14
pnpm-lock.yaml
generated
@@ -52,8 +52,8 @@ catalogs:
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
'@capp/eden':
|
||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz
|
||||
version: 0.0.5
|
||||
specifier: http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz
|
||||
version: 0.0.6
|
||||
'@cloudflare/workers-types':
|
||||
specifier: ^4.20260113.0
|
||||
version: 4.20260116.0
|
||||
@@ -298,7 +298,7 @@ importers:
|
||||
version: 8.0.0(@capacitor/core@8.0.0)
|
||||
'@capp/eden':
|
||||
specifier: 'catalog:'
|
||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||
version: http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))
|
||||
'@elysiajs/eden':
|
||||
specifier: 'catalog:'
|
||||
version: 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
@@ -1182,9 +1182,9 @@ packages:
|
||||
'@capacitor/synapse@1.0.4':
|
||||
resolution: {integrity: sha512-/C1FUo8/OkKuAT4nCIu/34ny9siNHr9qtFezu4kxm6GY1wNFxrCFWjfYx5C1tUhVGz3fxBABegupkpjXvjCHrw==}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz':
|
||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz}
|
||||
version: 0.0.5
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz':
|
||||
resolution: {tarball: http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz}
|
||||
version: 0.0.6
|
||||
peerDependencies:
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
|
||||
@@ -6903,7 +6903,7 @@ snapshots:
|
||||
|
||||
'@capacitor/synapse@1.0.4': {}
|
||||
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
'@capp/eden@http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz(@elysiajs/eden@1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3)))':
|
||||
dependencies:
|
||||
'@elysiajs/eden': 1.4.6(elysia@1.4.22(@sinclair/typebox@0.34.47)(exact-mirror@0.2.6(@sinclair/typebox@0.34.47))(file-type@21.3.0)(openapi-types@12.1.3)(typescript@5.9.3))
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ catalog:
|
||||
'@capacitor/keyboard': 8.0.0
|
||||
'@capacitor/share': ^8.0.0
|
||||
'@capacitor/status-bar': 8.0.0
|
||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.5.tgz
|
||||
'@capp/eden': http://192.168.1.2:9538/api/capp-eden-0.0.6.tgz
|
||||
'@cloudflare/workers-types': ^4.20260113.0
|
||||
'@elysiajs/eden': ^1.4.6
|
||||
'@faker-js/faker': ^10.2.0
|
||||
|
||||
@@ -79,6 +79,21 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import("@/views/payment/add.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/security",
|
||||
component: () => import("@/views/security/index.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/security/change_password",
|
||||
component: () => import("@/views/security/change_password.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/security/change_payment_password",
|
||||
component: () => import("@/views/security/change_payment_password.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -27,7 +27,7 @@ export const myApps = [
|
||||
name: "在线客服",
|
||||
icon: chatbubblesOutline,
|
||||
color: "#c32120",
|
||||
path: "/customer_service",
|
||||
path: "https://chat.riwsan.com",
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang='ts' setup>
|
||||
import type { MyApp } from "./";
|
||||
import { alertController } from "@ionic/vue";
|
||||
import { arrowDownOutline, arrowUpOutline, exitOutline, homeOutline, walletOutline } from "ionicons/icons";
|
||||
import { authClient } from "@/auth";
|
||||
@@ -19,7 +20,14 @@ function handleRecharge() {
|
||||
function handleWithdraw() {
|
||||
router.push("/withdraw");
|
||||
}
|
||||
|
||||
function handleClickApp(app: MyApp) {
|
||||
if (app.path.startsWith("http")) {
|
||||
window.open(app.path, "_blank");
|
||||
}
|
||||
else {
|
||||
router.push(app.path);
|
||||
}
|
||||
}
|
||||
async function handleLogout() {
|
||||
const alert = await alertController.create({
|
||||
header: "退出登录",
|
||||
@@ -139,7 +147,7 @@ async function handleLogout() {
|
||||
v-for="app in myApps"
|
||||
:key="app.id"
|
||||
class="flex flex-col items-center gap-2 cursor-pointer transition-transform active:scale-95"
|
||||
@click="$router.push(app.path)"
|
||||
@click="handleClickApp(app)"
|
||||
>
|
||||
<div
|
||||
class="app-item w-13 h-13 rounded-xl flex-center shadow-md"
|
||||
|
||||
289
src/views/security/change_password.vue
Normal file
289
src/views/security/change_password.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<script lang='ts' setup>
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { eyeOffOutline, eyeOutline, lockClosedOutline } from "ionicons/icons";
|
||||
import zod from "zod";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const formData = ref({
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
|
||||
const showCurrentPassword = ref(false);
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 表单验证 Schema
|
||||
const ChangePasswordSchema = zod.object({
|
||||
currentPassword: zod
|
||||
.string()
|
||||
.min(1, "请输入当前密码"),
|
||||
|
||||
newPassword: zod
|
||||
.string()
|
||||
.min(6, "新密码至少6位")
|
||||
.max(20, "新密码最多20位")
|
||||
.regex(/^(?=.*[a-z])(?=.*\d).+$/i, "新密码必须包含字母和数字"),
|
||||
|
||||
confirmPassword: zod
|
||||
.string()
|
||||
.min(1, "请确认新密码"),
|
||||
}).refine(data => data.newPassword === data.confirmPassword, {
|
||||
message: "两次输入的密码不一致",
|
||||
path: ["confirmPassword"],
|
||||
}).refine(data => data.currentPassword !== data.newPassword, {
|
||||
message: "新密码不能与当前密码相同",
|
||||
path: ["newPassword"],
|
||||
});
|
||||
|
||||
async function showToast(message: string, color: "success" | "danger" | "warning" = "danger") {
|
||||
const toast = await toastController.create({
|
||||
message,
|
||||
duration: 2000,
|
||||
position: "top",
|
||||
color,
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const result = ChangePasswordSchema.safeParse(formData.value);
|
||||
if (!result.success) {
|
||||
const first = result.error.issues[0];
|
||||
await showToast(first.message);
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
// TODO: 调用更改登录密码 API
|
||||
// const { data } = await safeClient(client.api.user.password.put({
|
||||
// currentPassword: formData.value.currentPassword,
|
||||
// newPassword: formData.value.newPassword,
|
||||
// }));
|
||||
|
||||
// 模拟 API 调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
await showToast("登录密码修改成功,请使用新密码登录", "success");
|
||||
router.back();
|
||||
}
|
||||
catch (error) {
|
||||
await showToast("密码修改失败,请检查当前密码是否正确", "danger");
|
||||
}
|
||||
finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ion-buttons slot="start">
|
||||
<back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>更改登录密码</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div class="space-y-5">
|
||||
<!-- 提示信息 -->
|
||||
<div class="info-card">
|
||||
<ion-icon :icon="lockClosedOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<div class="text-sm text-[#666] leading-relaxed">
|
||||
为了保障您的账户安全,请定期更换登录密码。密码修改成功后需要重新登录。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单卡片 -->
|
||||
<div class="form-card">
|
||||
<div class="space-y-4">
|
||||
<!-- 当前密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="lockClosedOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">当前密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.currentPassword"
|
||||
:type="showCurrentPassword ? 'text' : 'password'"
|
||||
placeholder="请输入当前登录密码"
|
||||
class="custom-input"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showCurrentPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showCurrentPassword = !showCurrentPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<!-- 新密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="lockClosedOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">新密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.newPassword"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
placeholder="请输入新密码(6-20位)"
|
||||
class="custom-input"
|
||||
:maxlength="20"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showNewPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
<div class="text-xs text-[#999] mt-1 ml-1">
|
||||
密码必须包含字母和数字,长度6-20位
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认新密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="lockClosedOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">确认新密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.confirmPassword"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
placeholder="请再次输入新密码"
|
||||
class="custom-input"
|
||||
:maxlength="20"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showConfirmPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<ion-button
|
||||
expand="block"
|
||||
class="submit-button"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<ion-spinner v-if="isSubmitting" name="crescent" />
|
||||
<span v-else>确认修改</span>
|
||||
</ion-button>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
<div class="notice-card">
|
||||
<div class="text-sm font-semibold text-[#333] mb-2">
|
||||
注意事项:
|
||||
</div>
|
||||
<ul class="text-xs text-[#666] space-y-1 pl-4">
|
||||
<li>• 密码长度为6-20位,必须包含字母和数字</li>
|
||||
<li>• 新密码不能与当前密码相同</li>
|
||||
<li>• 建议使用大小写字母、数字的组合</li>
|
||||
<li>• 密码修改成功后需要重新登录</li>
|
||||
<li>• 如忘记密码,请联系客服找回</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
.info-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ffe0e0;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
--background: #f7f8fa;
|
||||
--border-radius: 12px;
|
||||
--padding-start: 16px;
|
||||
--padding-end: 16px;
|
||||
--min-height: 48px;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
--placeholder-color: #999;
|
||||
--placeholder-opacity: 1;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
--background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
||||
--background-activated: linear-gradient(135deg, #8b1a2e 0%, #c41e3a 100%);
|
||||
--border-radius: 12px;
|
||||
--padding-top: 14px;
|
||||
--padding-bottom: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-top: 8px;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
background: #f9fafb;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.notice-card ul li {
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
295
src/views/security/change_payment_password.vue
Normal file
295
src/views/security/change_payment_password.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<script lang='ts' setup>
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { eyeOffOutline, eyeOutline, keyOutline } from "ionicons/icons";
|
||||
import zod from "zod";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const formData = ref({
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
|
||||
const showCurrentPassword = ref(false);
|
||||
const showNewPassword = ref(false);
|
||||
const showConfirmPassword = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
// 表单验证 Schema
|
||||
const ChangePaymentPasswordSchema = zod.object({
|
||||
currentPassword: zod
|
||||
.string()
|
||||
.min(1, "请输入当前交易密码"),
|
||||
|
||||
newPassword: zod
|
||||
.string()
|
||||
.min(6, "新密码至少6位")
|
||||
.max(20, "新密码最多20位")
|
||||
.regex(/^\d+$/, "交易密码只能包含数字")
|
||||
.refine(val => val.length === 6, "交易密码必须为6位数字"),
|
||||
|
||||
confirmPassword: zod
|
||||
.string()
|
||||
.min(1, "请确认新密码"),
|
||||
}).refine(data => data.newPassword === data.confirmPassword, {
|
||||
message: "两次输入的密码不一致",
|
||||
path: ["confirmPassword"],
|
||||
}).refine(data => data.currentPassword !== data.newPassword, {
|
||||
message: "新密码不能与当前密码相同",
|
||||
path: ["newPassword"],
|
||||
});
|
||||
|
||||
async function showToast(message: string, color: "success" | "danger" | "warning" = "danger") {
|
||||
const toast = await toastController.create({
|
||||
message,
|
||||
duration: 2000,
|
||||
position: "top",
|
||||
color,
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
const result = ChangePaymentPasswordSchema.safeParse(formData.value);
|
||||
if (!result.success) {
|
||||
const first = result.error.issues[0];
|
||||
await showToast(first.message);
|
||||
return;
|
||||
}
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
// TODO: 调用更改交易密码 API
|
||||
// const { data } = await safeClient(client.api.user.payment_password.put({
|
||||
// currentPassword: formData.value.currentPassword,
|
||||
// newPassword: formData.value.newPassword,
|
||||
// }));
|
||||
|
||||
// 模拟 API 调用
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
await showToast("交易密码修改成功", "success");
|
||||
router.back();
|
||||
}
|
||||
catch (error) {
|
||||
await showToast("密码修改失败,请检查当前密码是否正确", "danger");
|
||||
}
|
||||
finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ion-buttons slot="start">
|
||||
<back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>更改交易密码</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div class="space-y-5">
|
||||
<!-- 提示信息 -->
|
||||
<div class="info-card">
|
||||
<ion-icon :icon="keyOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<div class="text-sm text-[#666] leading-relaxed">
|
||||
交易密码用于提现、转账等资金操作,为了保障您的资金安全,请妥善保管交易密码。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 表单卡片 -->
|
||||
<div class="form-card">
|
||||
<div class="space-y-4">
|
||||
<!-- 当前交易密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="keyOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">当前交易密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.currentPassword"
|
||||
:type="showCurrentPassword ? 'text' : 'password'"
|
||||
placeholder="请输入当前交易密码"
|
||||
class="custom-input"
|
||||
inputmode="numeric"
|
||||
:maxlength="6"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showCurrentPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showCurrentPassword = !showCurrentPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
||||
<!-- 新交易密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="keyOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">新交易密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.newPassword"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
placeholder="请输入6位数字密码"
|
||||
class="custom-input"
|
||||
inputmode="numeric"
|
||||
:maxlength="6"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showNewPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showNewPassword = !showNewPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
<div class="text-xs text-[#999] mt-1 ml-1">
|
||||
交易密码为6位纯数字
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认新交易密码 -->
|
||||
<div class="form-item">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<ion-icon :icon="keyOutline" class="text-lg text-primary" />
|
||||
<label class="form-label">确认新密码</label>
|
||||
</div>
|
||||
<ion-item lines="none" class="input-item">
|
||||
<ion-input
|
||||
v-model="formData.confirmPassword"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
placeholder="请再次输入新密码"
|
||||
class="custom-input"
|
||||
inputmode="numeric"
|
||||
:maxlength="6"
|
||||
/>
|
||||
<ion-icon
|
||||
slot="end"
|
||||
:icon="showConfirmPassword ? eyeOutline : eyeOffOutline"
|
||||
class="password-toggle"
|
||||
@click="showConfirmPassword = !showConfirmPassword"
|
||||
/>
|
||||
</ion-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<ion-button
|
||||
expand="block"
|
||||
class="submit-button"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
<ion-spinner v-if="isSubmitting" name="crescent" />
|
||||
<span v-else>确认修改</span>
|
||||
</ion-button>
|
||||
|
||||
<!-- 注意事项 -->
|
||||
<div class="notice-card">
|
||||
<div class="text-sm font-semibold text-[#333] mb-2">
|
||||
注意事项:
|
||||
</div>
|
||||
<ul class="text-xs text-[#666] space-y-1 pl-4">
|
||||
<li>• 交易密码为6位纯数字,便于快速输入</li>
|
||||
<li>• 新密码不能与当前密码相同</li>
|
||||
<li>• 建议不要使用简单数字组合(如123456)</li>
|
||||
<li>• 交易密码与登录密码应设置不同</li>
|
||||
<li>• 请勿将交易密码告知他人</li>
|
||||
<li>• 如忘记密码,请联系客服找回</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
.info-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ffe0e0;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
--background: #f7f8fa;
|
||||
--border-radius: 12px;
|
||||
--padding-start: 16px;
|
||||
--padding-end: 16px;
|
||||
--min-height: 48px;
|
||||
}
|
||||
|
||||
.custom-input {
|
||||
--placeholder-color: #999;
|
||||
--placeholder-opacity: 1;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
--background: linear-gradient(135deg, #c41e3a 0%, #8b1a2e 100%);
|
||||
--background-activated: linear-gradient(135deg, #8b1a2e 0%, #c41e3a 100%);
|
||||
--border-radius: 12px;
|
||||
--padding-top: 14px;
|
||||
--padding-bottom: 14px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
margin-top: 8px;
|
||||
text-transform: none;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
background: #f9fafb;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.notice-card ul li {
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
286
src/views/security/index.vue
Normal file
286
src/views/security/index.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<script lang='ts' setup>
|
||||
import { toastController } from "@ionic/vue";
|
||||
import { checkmarkCircleOutline, chevronForwardOutline, closeCircleOutline, keyOutline, lockClosedOutline, shieldCheckmarkOutline } from "ionicons/icons";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// 交易密码是否已启用
|
||||
const hasPaymentPassword = ref(true);
|
||||
|
||||
async function showToast(message: string, color: "success" | "danger" | "warning" = "success") {
|
||||
const toast = await toastController.create({
|
||||
message,
|
||||
duration: 2000,
|
||||
position: "top",
|
||||
color,
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
function handleChangePassword() {
|
||||
router.push("/security/change_password");
|
||||
}
|
||||
|
||||
function handleChangePaymentPassword() {
|
||||
router.push("/security/change_payment_password");
|
||||
}
|
||||
|
||||
async function handleTogglePaymentPassword() {
|
||||
if (hasPaymentPassword.value) {
|
||||
// TODO: 禁用交易密码需要验证当前交易密码
|
||||
await showToast("交易密码功能已关闭", "warning");
|
||||
}
|
||||
else {
|
||||
// TODO: 启用交易密码需要设置新密码
|
||||
await showToast("交易密码功能已开启");
|
||||
}
|
||||
hasPaymentPassword.value = !hasPaymentPassword.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header class="ion-no-border">
|
||||
<ion-toolbar class="ion-toolbar">
|
||||
<ion-buttons slot="start">
|
||||
<back-button />
|
||||
</ion-buttons>
|
||||
<ion-title>安全中心</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">
|
||||
<div class="space-y-5">
|
||||
<!-- 安全提示 -->
|
||||
<div class="info-card">
|
||||
<ion-icon :icon="shieldCheckmarkOutline" class="text-2xl text-[#c41e3a]" />
|
||||
<div class="text-sm text-[#666] leading-relaxed">
|
||||
为了保障您的账户安全,建议定期更新密码,并启用交易密码保护。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安全设置列表 -->
|
||||
<div class="security-card">
|
||||
<div class="card-title">
|
||||
<ion-icon :icon="lockClosedOutline" class="text-xl text-primary" />
|
||||
<span>密码管理</span>
|
||||
</div>
|
||||
|
||||
<div class="security-list">
|
||||
<!-- 交易密码状态 -->
|
||||
<div class="security-item">
|
||||
<div class="item-left">
|
||||
<ion-icon :icon="keyOutline" class="item-icon text-primary" />
|
||||
<div class="item-content">
|
||||
<div class="item-title">
|
||||
交易密码
|
||||
</div>
|
||||
<div class="item-desc">
|
||||
用于提现、转账等资金操作
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="status-badge" :class="hasPaymentPassword ? 'enabled' : 'disabled'">
|
||||
<ion-icon
|
||||
:icon="hasPaymentPassword ? checkmarkCircleOutline : closeCircleOutline"
|
||||
class="text-sm"
|
||||
/>
|
||||
<span>{{ hasPaymentPassword ? '已启用' : '未启用' }}</span>
|
||||
</div>
|
||||
<ion-toggle
|
||||
:checked="hasPaymentPassword"
|
||||
color="danger"
|
||||
@ion-change="handleTogglePaymentPassword"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更改登录密码 -->
|
||||
<div class="security-item clickable" @click="handleChangePassword">
|
||||
<div class="item-left">
|
||||
<ion-icon :icon="lockClosedOutline" class="item-icon text-primary" />
|
||||
<div class="item-content">
|
||||
<div class="item-title">
|
||||
更改登录密码
|
||||
</div>
|
||||
<div class="item-desc">
|
||||
定期更改密码,保护账户安全
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<ion-icon :icon="chevronForwardOutline" class="text-[#999]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 更改交易密码 -->
|
||||
<div
|
||||
class="security-item clickable"
|
||||
:class="{ disabled: !hasPaymentPassword }"
|
||||
@click="hasPaymentPassword && handleChangePaymentPassword()"
|
||||
>
|
||||
<div class="item-left">
|
||||
<ion-icon :icon="keyOutline" class="item-icon text-primary" />
|
||||
<div class="item-content">
|
||||
<div class="item-title">
|
||||
更改交易密码
|
||||
</div>
|
||||
<div class="item-desc">
|
||||
修改交易密码,提升资金安全
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-right">
|
||||
<ion-icon :icon="chevronForwardOutline" class="text-[#999]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 安全建议 -->
|
||||
<div class="notice-card">
|
||||
<div class="text-sm font-semibold text-[#333] mb-2">
|
||||
安全建议:
|
||||
</div>
|
||||
<ul class="text-xs text-[#666] space-y-1 pl-4">
|
||||
<li>• 密码长度建议8位以上,包含字母和数字</li>
|
||||
<li>• 不要使用生日、电话等易被猜测的密码</li>
|
||||
<li>• 登录密码和交易密码建议设置不同</li>
|
||||
<li>• 不要将密码告诉他人或在公共场合输入</li>
|
||||
<li>• 如发现账户异常,请立即联系客服</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
.info-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
|
||||
border-radius: 12px;
|
||||
border: 1px solid #ffe0e0;
|
||||
}
|
||||
|
||||
.security-card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.security-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.security-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: #f7f8fa;
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.security-item.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.security-item.clickable:active {
|
||||
transform: scale(0.98);
|
||||
background: #eef0f2;
|
||||
}
|
||||
|
||||
.security-item.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.security-item.disabled:active {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.item-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.item-desc {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-badge.enabled {
|
||||
background: #f0f9ff;
|
||||
color: #0284c7;
|
||||
}
|
||||
|
||||
.status-badge.disabled {
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
background: #f9fafb;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.notice-card ul li {
|
||||
line-height: 1.8;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user