383 lines
12 KiB
Vue
383 lines
12 KiB
Vue
<template>
|
||
<section
|
||
class="m-page m-page--white m-subpage"
|
||
:class="{ 'm-subpage--profile': formType === 'profile' }"
|
||
:style="
|
||
formType === 'profile'
|
||
? { backgroundImage: `url(${mobileAssets.profileBackground})` }
|
||
: undefined
|
||
"
|
||
>
|
||
<PageHeader :title="title" />
|
||
<AsyncState
|
||
v-if="formType === 'profile'"
|
||
:loading="loading"
|
||
:error="error"
|
||
@retry="loadProfile"
|
||
>
|
||
<div class="m-profile-edit">
|
||
<div class="m-avatar m-avatar--large"
|
||
><img
|
||
v-if="safeImage(me?.avatar_url)"
|
||
:src="safeImage(me.avatar_url)"
|
||
alt="用户头像" /><n-icon v-else :component="PersonCircleOutline" /><img
|
||
class="m-avatar-edit"
|
||
:src="mobileAssets.avatarEdit"
|
||
alt=""
|
||
/></div>
|
||
<div class="m-info-card m-card">
|
||
<div
|
||
><span>昵称</span><b>{{ me?.username }}</b></div
|
||
><div
|
||
><span>手机号码</span><b>{{ me?.phone }}</b></div
|
||
><div
|
||
><span>邀请码</span><b>{{ me?.invite_code }}</b></div
|
||
>
|
||
<router-link to="/app/profile/password"
|
||
><span>密码管理</span><n-icon :component="ChevronForwardOutline"
|
||
/></router-link>
|
||
<router-link to="/app/profile/trade-password"
|
||
><span>交易密码管理</span><n-icon :component="ChevronForwardOutline"
|
||
/></router-link>
|
||
</div>
|
||
</div>
|
||
</AsyncState>
|
||
|
||
<form v-else-if="formType === 'bank'" class="m-plain-form" @submit.prevent="saveBank">
|
||
<label
|
||
><span>姓名</span><input v-model.trim="bank.holder_name" placeholder="请输入姓名"
|
||
/></label>
|
||
<label
|
||
><span>银行卡号</span
|
||
><input v-model.trim="bank.account_no" inputmode="numeric" placeholder="请输入银行卡号"
|
||
/></label>
|
||
<label
|
||
><span>开户行</span><input v-model.trim="bank.bank_name" placeholder="请输入开户行"
|
||
/></label>
|
||
<label
|
||
><span>银行编码</span><input v-model.trim="bank.bank_code" placeholder="例如 ICBC"
|
||
/></label>
|
||
<label
|
||
><span>预留手机号</span
|
||
><input
|
||
v-model.trim="bank.reserved_phone"
|
||
inputmode="numeric"
|
||
maxlength="11"
|
||
placeholder="请输入预留手机号(选填)"
|
||
/></label>
|
||
<label
|
||
><span>交易密码</span
|
||
><input
|
||
v-model="bank.trade_password"
|
||
type="password"
|
||
inputmode="numeric"
|
||
maxlength="6"
|
||
placeholder="请输入 6 位交易密码"
|
||
/></label>
|
||
<p class="m-form-hint">微信、支付宝二维码上传能力将在上传接口开放后提供。</p>
|
||
<p v-if="error" class="m-form-error">{{ error }}</p
|
||
><button class="m-primary-btn" :disabled="submitting">{{
|
||
submitting ? '提交中...' : '确认绑定'
|
||
}}</button>
|
||
</form>
|
||
|
||
<form
|
||
v-else-if="formType === 'tradePassword'"
|
||
class="m-password-form"
|
||
@submit.prevent="saveTradePassword"
|
||
>
|
||
<h2>设置支付密码</h2><p>支付密码用于收款账户绑定和取出确认</p>
|
||
<div class="m-code-input"
|
||
><input
|
||
v-model="trade.new_trade_password"
|
||
type="password"
|
||
inputmode="numeric"
|
||
maxlength="6"
|
||
placeholder="请输入 6 位数字"
|
||
/></div>
|
||
<label class="m-input-row"
|
||
><n-icon :component="LockClosedOutline" /><input
|
||
v-model="trade.login_password"
|
||
type="password"
|
||
placeholder="请输入登录密码确认"
|
||
/></label>
|
||
<label class="m-input-row"
|
||
><n-icon :component="KeyOutline" /><input
|
||
v-model="trade.old_trade_password"
|
||
type="password"
|
||
inputmode="numeric"
|
||
maxlength="6"
|
||
placeholder="已设置过时请输入原支付密码"
|
||
/></label>
|
||
<p v-if="error" class="m-form-error">{{ error }}</p
|
||
><button class="m-primary-btn" :disabled="submitting">{{
|
||
submitting ? '提交中...' : '确定'
|
||
}}</button>
|
||
</form>
|
||
|
||
<form
|
||
v-else-if="formType === 'loginPassword'"
|
||
class="m-password-form"
|
||
@submit.prevent="saveLoginPassword"
|
||
>
|
||
<h2>修改登录密码</h2><p>修改成功后需要重新登录</p>
|
||
<label class="m-input-row"
|
||
><n-icon :component="LockClosedOutline" /><input
|
||
v-model="loginPassword.old_password"
|
||
type="password"
|
||
autocomplete="current-password"
|
||
placeholder="请输入当前密码"
|
||
/></label>
|
||
<label class="m-input-row"
|
||
><n-icon :component="KeyOutline" /><input
|
||
v-model="loginPassword.new_password"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
placeholder="请输入新密码"
|
||
/></label>
|
||
<label class="m-input-row"
|
||
><n-icon :component="KeyOutline" /><input
|
||
v-model="loginPassword.confirm_password"
|
||
type="password"
|
||
autocomplete="new-password"
|
||
placeholder="请再次输入新密码"
|
||
/></label>
|
||
<p v-if="error" class="m-form-error">{{ error }}</p>
|
||
<button class="m-primary-btn" :disabled="submitting">{{
|
||
submitting ? '提交中...' : '确定修改'
|
||
}}</button>
|
||
</form>
|
||
|
||
<form v-else-if="formType === 'verify'" class="m-plain-form m-verify" @submit.prevent="goReset">
|
||
<label
|
||
><span>手机号</span
|
||
><input
|
||
v-model.trim="verify.phone"
|
||
inputmode="numeric"
|
||
maxlength="11"
|
||
placeholder="请输入手机号"
|
||
/></label>
|
||
<label
|
||
><span>验证码</span
|
||
><input
|
||
v-model.trim="verify.code"
|
||
inputmode="numeric"
|
||
maxlength="6"
|
||
placeholder="请输入验证码"
|
||
/><button type="button" @click="notifyUnavailable">获取验证码</button></label
|
||
>
|
||
<p v-if="error" class="m-form-error">{{ error }}</p
|
||
><button class="m-primary-btn">确定</button>
|
||
</form>
|
||
|
||
<form
|
||
v-else-if="formType === 'reset'"
|
||
class="m-password-form"
|
||
@submit.prevent="notifyUnavailable"
|
||
>
|
||
<h2>重设密码</h2><p>请输入新的登录密码</p
|
||
><label class="m-input-row"
|
||
><n-icon :component="LockClosedOutline" /><input
|
||
v-model="resetPassword"
|
||
type="password"
|
||
placeholder="请输入新密码" /></label
|
||
><button class="m-primary-btn">确定</button>
|
||
</form>
|
||
|
||
<AsyncState
|
||
v-else
|
||
:loading="loading"
|
||
:error="error"
|
||
:empty="!content"
|
||
empty-text="暂无内容"
|
||
@retry="loadContent"
|
||
>
|
||
<article v-if="content" class="m-content"
|
||
><h1>{{ content.title }}</h1
|
||
><p class="m-content-summary">{{ content.summary }}</p
|
||
><div class="m-content-body">{{ content.body_text || '暂无正文' }}</div
|
||
><time v-if="content.published_at"
|
||
>发布时间:{{ dateTime(content.published_at) }}</time
|
||
></article
|
||
>
|
||
</AsyncState>
|
||
</section>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, onMounted, reactive, ref } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import {
|
||
ChevronForwardOutline,
|
||
KeyOutline,
|
||
LockClosedOutline,
|
||
PersonCircleOutline,
|
||
} from '@vicons/ionicons5';
|
||
import PageHeader from '../components/PageHeader.vue';
|
||
import AsyncState from '../components/AsyncState.vue';
|
||
import { clearSession, dateTime, errorText, request, safeImage } from '../api';
|
||
import { mobileAssets } from '../assets';
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
const formType = computed(() => String(route.meta.form || ''));
|
||
const contentTitles: Record<string, string> = {
|
||
recharge_records: '储入记录',
|
||
withdrawal_records: '取出记录',
|
||
wallet_ledgers: '账户明细',
|
||
notices: '弹窗明细',
|
||
};
|
||
const title = computed(
|
||
() =>
|
||
content.value?.title ||
|
||
contentTitles[String(route.params.key || '')] ||
|
||
String(route.meta.title || '')
|
||
);
|
||
const loading = ref(false);
|
||
const submitting = ref(false);
|
||
const error = ref('');
|
||
const me = ref<any>();
|
||
const content = ref<any>();
|
||
const resetPassword = ref('');
|
||
const bank = reactive({
|
||
account_type: 'bank_card',
|
||
bank_code: '',
|
||
bank_name: '',
|
||
holder_name: '',
|
||
account_no: '',
|
||
reserved_phone: '',
|
||
trade_password: '',
|
||
qr_attachment_id: 0,
|
||
});
|
||
const trade = reactive({ login_password: '', old_trade_password: '', new_trade_password: '' });
|
||
const loginPassword = reactive({ old_password: '', new_password: '', confirm_password: '' });
|
||
const verify = reactive({ phone: '', code: '' });
|
||
async function loadProfile() {
|
||
loading.value = true;
|
||
error.value = '';
|
||
try {
|
||
me.value = await request({ url: '/v1/me' });
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
async function saveBank() {
|
||
error.value = '';
|
||
if (
|
||
!bank.holder_name ||
|
||
!/^[0-9 -]{8,36}$/.test(bank.account_no) ||
|
||
!bank.bank_name ||
|
||
!/^[0-9]{6}$/.test(bank.trade_password)
|
||
) {
|
||
error.value = '请完整填写姓名、银行卡、开户行和 6 位交易密码';
|
||
return;
|
||
}
|
||
submitting.value = true;
|
||
try {
|
||
await request({ url: '/v1/payment-accounts', method: 'POST', data: bank });
|
||
window.$message?.success('提交成功,等待后台核验');
|
||
router.back();
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
submitting.value = false;
|
||
}
|
||
}
|
||
async function saveTradePassword() {
|
||
error.value = '';
|
||
if (!/^\d{6}$/.test(trade.new_trade_password) || trade.login_password.length < 6) {
|
||
error.value = '请输入 6 位支付密码并填写登录密码';
|
||
return;
|
||
}
|
||
submitting.value = true;
|
||
try {
|
||
await request({
|
||
url: '/v1/me/trade-password',
|
||
method: 'PUT',
|
||
data: { ...trade, old_trade_password: trade.old_trade_password || undefined },
|
||
});
|
||
window.$message?.success('支付密码设置成功');
|
||
router.back();
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
submitting.value = false;
|
||
}
|
||
}
|
||
async function saveLoginPassword() {
|
||
error.value = '';
|
||
if (loginPassword.old_password.length < 6 || loginPassword.new_password.length < 6) {
|
||
error.value = '登录密码至少为 6 位';
|
||
return;
|
||
}
|
||
if (loginPassword.new_password !== loginPassword.confirm_password) {
|
||
error.value = '两次输入的新密码不一致';
|
||
return;
|
||
}
|
||
submitting.value = true;
|
||
try {
|
||
await request({
|
||
url: '/v1/me/password',
|
||
method: 'PUT',
|
||
data: {
|
||
old_password: loginPassword.old_password,
|
||
new_password: loginPassword.new_password,
|
||
},
|
||
});
|
||
clearSession();
|
||
window.$message?.success('密码修改成功,请重新登录');
|
||
await router.replace('/app/auth');
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
submitting.value = false;
|
||
}
|
||
}
|
||
function notifyUnavailable() {
|
||
window.$message?.warning('短信验证接口尚未开放');
|
||
}
|
||
function goReset() {
|
||
if (!/^1\d{10}$/.test(verify.phone) || !/^\d{4,6}$/.test(verify.code)) {
|
||
error.value = '请输入正确的手机号和验证码';
|
||
return;
|
||
}
|
||
notifyUnavailable();
|
||
}
|
||
async function loadContent() {
|
||
loading.value = true;
|
||
error.value = '';
|
||
const key = String(route.params.key || '');
|
||
if (['recharge_records', 'wallet_ledgers', 'notices'].includes(key)) {
|
||
loading.value = false;
|
||
return;
|
||
}
|
||
try {
|
||
if (key === 'withdrawal_records') {
|
||
const data = await request<any>({
|
||
url: '/v1/withdrawal-orders',
|
||
params: { page: 1, page_size: 20, status: 'all' },
|
||
});
|
||
content.value = {
|
||
title: '取出记录',
|
||
body_text: data.items?.length
|
||
? data.items
|
||
.map((x: any) => `${dateTime(x.created_at)} ${x.status_text || x.status}`)
|
||
.join('\n')
|
||
: '暂无取出记录',
|
||
};
|
||
} else {
|
||
content.value = await request({ url: `/v1/contents/${key}` });
|
||
}
|
||
} catch (err) {
|
||
error.value = errorText(err);
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
onMounted(() => {
|
||
if (formType.value === 'profile') loadProfile();
|
||
else if (formType.value === 'content') loadContent();
|
||
});
|
||
</script>
|