商城c端前端
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user