Files
olons_server/web/src/mobile/views/FormView.vue
T
2026-10-12 23:40:02 +08:00

383 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>