feat:项目中心和个人中心
This commit is contained in:
22
src/constants/account-security.ts
Normal file
22
src/constants/account-security.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { AccountSecurityItem } from '@/types/account-security'
|
||||
|
||||
export const ACCOUNT_SECURITY_ITEMS: AccountSecurityItem[] = [
|
||||
{
|
||||
key: 'password',
|
||||
title: '修改密码',
|
||||
subtitle: '上次修改:15天前',
|
||||
status: '已设置',
|
||||
},
|
||||
{
|
||||
key: 'phone',
|
||||
title: '绑定手机',
|
||||
subtitle: '186****9618',
|
||||
status: '已绑定',
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
title: '绑定邮箱',
|
||||
subtitle: 'yongzh******4@163.com',
|
||||
status: '已绑定',
|
||||
},
|
||||
]
|
||||
@@ -25,8 +25,13 @@ function buildBillList(): BillItem[] {
|
||||
bills.push({
|
||||
...plan,
|
||||
projectId: project.id,
|
||||
billNo: plan.id === '1' && project.id === '1' ? '20260813ZZ251245' : plan.billNo,
|
||||
transactionNo: paidMeta?.transactionNo || plan.transactionNo,
|
||||
billNo: ['1', '2', '3'].includes(plan.id) && project.id === '1'
|
||||
? '20260813ZZ251245'
|
||||
: plan.billNo,
|
||||
transactionNo: paidMeta?.transactionNo
|
||||
|| (['2', '3'].includes(plan.id) && project.id === '1'
|
||||
? 'T20260819153035678K2M001'
|
||||
: plan.transactionNo),
|
||||
paidAt: paidMeta?.paidAt,
|
||||
paidMethod: paidMeta?.paidMethod,
|
||||
})
|
||||
|
||||
1
src/constants/bind-email.ts
Normal file
1
src/constants/bind-email.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const BOUND_EMAIL_MASKED = 'yongzh******4@163.com'
|
||||
3
src/constants/bind-phone.ts
Normal file
3
src/constants/bind-phone.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const BOUND_PHONE_MASKED = '186****9618'
|
||||
|
||||
export const BIND_PHONE_NEW_HINT = '注:换绑新手机号之后,可以用新的手机号及当前密码登录'
|
||||
1
src/constants/feedback.ts
Normal file
1
src/constants/feedback.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const FEEDBACK_DESCRIPTION_MAX_LENGTH = 500
|
||||
38
src/constants/finance-center.ts
Normal file
38
src/constants/finance-center.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { FinanceQuickAction } from '@/types/finance-center'
|
||||
import iconBillDetails from '@/static/icon/bill-details.svg'
|
||||
import iconPendingPayment from '@/static/icon/pending-payment.svg'
|
||||
import iconInvoiceApply from '@/static/icon/invoice-apply.svg'
|
||||
import iconReportExport from '@/static/icon/report-export.svg'
|
||||
|
||||
export const FINANCE_CENTER_TOTAL = 4000000
|
||||
|
||||
export const FINANCE_MONTH_OPTIONS = [
|
||||
'2026年1月',
|
||||
'2025年12月',
|
||||
'2025年11月',
|
||||
]
|
||||
|
||||
export const FINANCE_QUICK_ACTIONS: FinanceQuickAction[] = [
|
||||
{
|
||||
key: 'bill-list',
|
||||
label: '账单明细',
|
||||
icon: iconBillDetails,
|
||||
path: '/pages/project-center/bill-list/index',
|
||||
},
|
||||
{
|
||||
key: 'pending',
|
||||
label: '待支付',
|
||||
icon: iconPendingPayment,
|
||||
path: '/pages/project-center/bill-list/index',
|
||||
},
|
||||
{
|
||||
key: 'invoice',
|
||||
label: '发票申请',
|
||||
icon: iconInvoiceApply,
|
||||
},
|
||||
{
|
||||
key: 'export',
|
||||
label: '报表导出',
|
||||
icon: iconReportExport,
|
||||
},
|
||||
]
|
||||
@@ -20,11 +20,11 @@ export const PERSONAL_CENTER_PROFILE: PersonalCenterProfile = {
|
||||
}
|
||||
|
||||
export const PERSONAL_CENTER_MENU: PersonalCenterMenuItem[] = [
|
||||
{ key: 'security', label: '账号安全', icon: iconSecurity },
|
||||
{ key: 'security', label: '账号安全', icon: iconSecurity, path: '/pages/personal-center/account-security/index' },
|
||||
{ key: 'message-push', label: '消息推送', icon: iconMessagePush },
|
||||
{ key: 'feedback', label: '意见反馈', icon: iconFeedback },
|
||||
{ key: 'feedback', label: '意见反馈', icon: iconFeedback, path: '/pages/personal-center/feedback/index' },
|
||||
{ key: 'contract-list', label: '合同列表', icon: iconContractList },
|
||||
{ key: 'finance', label: '财务管理', icon: iconFinance, path: '/pages/project-center/bill-list/index' },
|
||||
{ key: 'finance', label: '财务中心', icon: iconFinance, path: '/pages/personal-center/finance/index' },
|
||||
{ key: 'invoice', label: '发票列表', icon: iconInvoice },
|
||||
{ key: 'address', label: '地址管理', icon: iconAddress },
|
||||
{ key: 'favorites', label: '收藏列表', icon: iconFavorites, path: '/pages/case-library/favorites/index' },
|
||||
|
||||
@@ -17,7 +17,7 @@ export const QUICK_ENTRY_CATALOG: QuickEntryItem[] = [
|
||||
{ id: 'report', label: '快捷提报', icon: '/static/icons/quick-report.png', bgColor: '#E8FAF4' },
|
||||
{ id: 'service', label: '在线客服', icon: '/static/icons/quick-service.png', bgColor: '#F3E8FF' },
|
||||
{ id: 'contract', label: '合同管理', icon: '/static/icons/quick-contract.png', bgColor: '#FEE2E2' },
|
||||
{ id: 'finance', label: '财务管理', icon: '/static/icons/quick-finance.png', bgColor: '#E0F2FE' },
|
||||
{ id: 'finance', label: '财务中心', icon: '/static/icons/quick-finance.png', bgColor: '#E0F2FE' },
|
||||
{ id: 'personal', label: '个人中心', icon: '/static/icons/tab-personals.png', bgColor: '#E8F0FF' },
|
||||
{ id: 'message', label: '消息中心', icon: '/static/icons/tab-messages.png', bgColor: '#F3E8FF' },
|
||||
{ id: 'security', label: '账号安全', icon: '/static/icons/security.png', bgColor: '#E8FAF4' },
|
||||
|
||||
@@ -150,6 +150,54 @@
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/account-security/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/change-password/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/bind-phone/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/bind-phone-new/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/bind-email/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/bind-email-change/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/feedback/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/finance/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.login-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
@@ -8,6 +9,15 @@
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.login-page__guest {
|
||||
position: absolute;
|
||||
top: calc(var(--status-bar-height, 44px) + 24rpx);
|
||||
right: 32rpx;
|
||||
z-index: 10;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-link;
|
||||
}
|
||||
|
||||
.login-page__header {
|
||||
padding-top: 120rpx;
|
||||
text-align: center;
|
||||
|
||||
@@ -47,10 +47,21 @@ function handleUserAgreement() {
|
||||
url: '/pages/Login/user-agreement/index',
|
||||
})
|
||||
}
|
||||
|
||||
function handleGuestMode() {
|
||||
uni.redirectTo({ url: '/pages/home-index/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<text
|
||||
class="login-page__guest"
|
||||
@tap="handleGuestMode"
|
||||
>
|
||||
游客模式
|
||||
</text>
|
||||
|
||||
<view class="login-page__header">
|
||||
<text class="login-page__title">
|
||||
欢迎回来
|
||||
|
||||
@@ -23,7 +23,12 @@ const redirectUrl = computed(() => {
|
||||
|
||||
const title = computed(() => pageConfig.value.title)
|
||||
|
||||
const subtitle = computed(() => `${countdown.value}秒后自动跳转至${pageConfig.value.subtitleTarget}`)
|
||||
const subtitle = computed(() => {
|
||||
if (pageType.value === 'modifySuccess')
|
||||
return `${countdown.value}秒后自动返回`
|
||||
|
||||
return `${countdown.value}秒后自动跳转至${pageConfig.value.subtitleTarget}`
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
const type = options?.type
|
||||
@@ -44,7 +49,10 @@ onLoad((options) => {
|
||||
const id = options?.id
|
||||
if (id)
|
||||
projectId.value = decodeURIComponent(String(id))
|
||||
return
|
||||
}
|
||||
if (type === 'modifySuccess')
|
||||
pageType.value = 'modifySuccess'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
|
||||
@@ -47,10 +47,8 @@
|
||||
}
|
||||
|
||||
.password-strength__error-mark {
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
}
|
||||
|
||||
.password-strength__error-text {
|
||||
|
||||
@@ -45,9 +45,11 @@ const showIndicator = computed(() => props.password.length > 0)
|
||||
class="password-strength__error"
|
||||
>
|
||||
<view class="password-strength__error-icon">
|
||||
<text class="password-strength__error-mark">
|
||||
<img src="/static/icons/icon-mark.png" alt="error" />
|
||||
</text>
|
||||
<image
|
||||
class="password-strength__error-mark"
|
||||
src="/static/icons/icon-mark.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="password-strength__error-text">
|
||||
密码等级过低,请重新输入
|
||||
|
||||
107
src/pages/personal-center/account-security/index.scss
Normal file
107
src/pages/personal-center/account-security/index.scss
Normal file
@@ -0,0 +1,107 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.account-security {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.account-security__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.account-security__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.account-security__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.account-security__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.account-security__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.account-security__content {
|
||||
flex: 1;
|
||||
padding: 24rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.account-security__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 48rpx;
|
||||
}
|
||||
|
||||
.account-security__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.account-security__item-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.account-security__item-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.account-security__item-subtitle {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.account-security__item-side {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.account-security__item-status {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.account-security__item-arrow {
|
||||
margin-left: 8rpx;
|
||||
font-size: 36rpx;
|
||||
line-height: 1;
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.account-security__delete-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 120rpx;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
103
src/pages/personal-center/account-security/index.vue
Normal file
103
src/pages/personal-center/account-security/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { ACCOUNT_SECURITY_ITEMS } from '@/constants/account-security'
|
||||
import type { AccountSecurityItem } from '@/types/account-security'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const securityItems = ACCOUNT_SECURITY_ITEMS
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleItemTap(item: AccountSecurityItem) {
|
||||
if (item.key === 'password') {
|
||||
uni.navigateTo({ url: '/pages/personal-center/change-password/index' })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.key === 'phone') {
|
||||
uni.navigateTo({ url: '/pages/personal-center/bind-phone/index' })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.key === 'email') {
|
||||
uni.navigateTo({ url: '/pages/personal-center/bind-email/index' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: `${item.title}开发中`, icon: 'none' })
|
||||
}
|
||||
|
||||
function handleDeleteAccount() {
|
||||
uni.showModal({
|
||||
title: '注销账号',
|
||||
content: '注销后账号数据将无法恢复,确认继续吗?',
|
||||
confirmText: '确认注销',
|
||||
confirmColor: '#2563EB',
|
||||
success: (res) => {
|
||||
if (res.confirm)
|
||||
uni.showToast({ title: '注销申请已提交', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="account-security">
|
||||
<view class="account-security__navbar">
|
||||
<view
|
||||
class="account-security__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="account-security__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="account-security__navbar-title">
|
||||
账号安全
|
||||
</text>
|
||||
<view class="account-security__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="account-security__content">
|
||||
<view class="account-security__list">
|
||||
<view
|
||||
v-for="item in securityItems"
|
||||
:key="item.key"
|
||||
class="account-security__item"
|
||||
@tap="handleItemTap(item)"
|
||||
>
|
||||
<view class="account-security__item-main">
|
||||
<text class="account-security__item-title">
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<text class="account-security__item-subtitle">
|
||||
{{ item.subtitle }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="account-security__item-side">
|
||||
<text class="account-security__item-status">
|
||||
{{ item.status }}
|
||||
</text>
|
||||
<text class="account-security__item-arrow">
|
||||
›
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="account-security__delete-btn"
|
||||
@tap="handleDeleteAccount"
|
||||
>
|
||||
注销账号
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
63
src/pages/personal-center/bind-email-change/index.scss
Normal file
63
src/pages/personal-center/bind-email-change/index.scss
Normal file
@@ -0,0 +1,63 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.bind-email-change {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-email-change__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-email-change__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-email-change__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.bind-email-change__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-email-change__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-email-change__content {
|
||||
padding: 48rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bind-email-change__field {
|
||||
width: 100%;
|
||||
margin-bottom: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-email-change__submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 48rpx;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
80
src/pages/personal-center/bind-email-change/index.vue
Normal file
80
src/pages/personal-center/bind-email-change/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import ForgotInput from '@/pages/Login/user-forgot-password/components/ForgotInput.vue'
|
||||
import CodeInput from '@/pages/Login/user-register/components/CodeInput.vue'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||
|
||||
const email = ref('')
|
||||
const code = ref('')
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/bind-email/index')
|
||||
}
|
||||
|
||||
function isValidEmail(value: string) {
|
||||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!email.value.trim()) {
|
||||
uni.showToast({ title: '请输入邮箱', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!isValidEmail(email.value.trim())) {
|
||||
uni.showToast({ title: '请输入正确的邮箱', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code.value.trim()) {
|
||||
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
navigateToResultPage('modifySuccess')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bind-email-change">
|
||||
<view class="bind-email-change__navbar">
|
||||
<view
|
||||
class="bind-email-change__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="bind-email-change__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="bind-email-change__navbar-title">
|
||||
绑定邮箱
|
||||
</text>
|
||||
<view class="bind-email-change__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="bind-email-change__content">
|
||||
<view class="bind-email-change__field">
|
||||
<ForgotInput
|
||||
v-model="email"
|
||||
placeholder="请输入邮箱"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="bind-email-change__field">
|
||||
<CodeInput v-model="code" />
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="bind-email-change__submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
验证并绑定邮箱
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
96
src/pages/personal-center/bind-email/index.scss
Normal file
96
src/pages/personal-center/bind-email/index.scss
Normal file
@@ -0,0 +1,96 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.bind-email {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-email__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-email__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-email__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.bind-email__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-email__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-email__content {
|
||||
padding: 48rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bind-email__field {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-email__label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-email__email {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
background: $color-bg-input;
|
||||
border-radius: 48rpx;
|
||||
}
|
||||
|
||||
.bind-email__email-text {
|
||||
font-size: 30rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-email__actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.bind-email__btn {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&--primary {
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
}
|
||||
}
|
||||
80
src/pages/personal-center/bind-email/index.vue
Normal file
80
src/pages/personal-center/bind-email/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { BOUND_EMAIL_MASKED } from '@/constants/bind-email'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const boundEmail = BOUND_EMAIL_MASKED
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/account-security/index')
|
||||
}
|
||||
|
||||
function handleDeleteEmail() {
|
||||
uni.showModal({
|
||||
title: '删除邮箱',
|
||||
content: '删除后将无法通过邮箱接收通知,确认删除吗?',
|
||||
confirmText: '确认删除',
|
||||
confirmColor: '#2563EB',
|
||||
success: (res) => {
|
||||
if (res.confirm)
|
||||
uni.showToast({ title: '邮箱已删除', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleChangeEmail() {
|
||||
uni.navigateTo({ url: '/pages/personal-center/bind-email-change/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bind-email">
|
||||
<view class="bind-email__navbar">
|
||||
<view
|
||||
class="bind-email__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="bind-email__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="bind-email__navbar-title">
|
||||
绑定邮箱
|
||||
</text>
|
||||
<view class="bind-email__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="bind-email__content">
|
||||
<view class="bind-email__field">
|
||||
<text class="bind-email__label">
|
||||
已绑定邮箱
|
||||
</text>
|
||||
<view class="bind-email__email">
|
||||
<text class="bind-email__email-text">
|
||||
{{ boundEmail }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-email__actions">
|
||||
<view
|
||||
class="bind-email__btn bind-email__btn--primary"
|
||||
@tap="handleDeleteEmail"
|
||||
>
|
||||
删除邮箱
|
||||
</view>
|
||||
<view
|
||||
class="bind-email__btn bind-email__btn--primary"
|
||||
@tap="handleChangeEmail"
|
||||
>
|
||||
更换绑定邮箱
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
79
src/pages/personal-center/bind-phone-new/index.scss
Normal file
79
src/pages/personal-center/bind-phone-new/index.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.bind-phone-new {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-phone-new__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-phone-new__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-phone-new__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.bind-phone-new__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-phone-new__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-phone-new__content {
|
||||
padding: 48rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bind-phone-new__field {
|
||||
width: 100%;
|
||||
margin-bottom: 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-phone-new__label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-phone-new__hint {
|
||||
display: block;
|
||||
margin-top: 8rpx;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.bind-phone-new__submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 48rpx;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
90
src/pages/personal-center/bind-phone-new/index.vue
Normal file
90
src/pages/personal-center/bind-phone-new/index.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { BIND_PHONE_NEW_HINT } from '@/constants/bind-phone'
|
||||
import CodeInput from '@/pages/Login/user-register/components/CodeInput.vue'
|
||||
import RegisterInput from '@/pages/Login/user-register/components/RegisterInput.vue'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||
|
||||
const hint = BIND_PHONE_NEW_HINT
|
||||
const newPhone = ref('')
|
||||
const code = ref('')
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/bind-phone/index')
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!newPhone.value.trim()) {
|
||||
uni.showToast({ title: '请输入新手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!/^1\d{10}$/.test(newPhone.value.trim())) {
|
||||
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!code.value.trim()) {
|
||||
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
navigateToResultPage('modifySuccess')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bind-phone-new">
|
||||
<view class="bind-phone-new__navbar">
|
||||
<view
|
||||
class="bind-phone-new__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="bind-phone-new__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="bind-phone-new__navbar-title">
|
||||
绑定手机号
|
||||
</text>
|
||||
<view class="bind-phone-new__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="bind-phone-new__content">
|
||||
<view class="bind-phone-new__field">
|
||||
<text class="bind-phone-new__label">
|
||||
请输入新手机号
|
||||
</text>
|
||||
<RegisterInput
|
||||
v-model="newPhone"
|
||||
icon="/static/icons/icon-phone.png"
|
||||
placeholder="请输入新手机号"
|
||||
input-type="number"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="bind-phone-new__field">
|
||||
<text class="bind-phone-new__label">
|
||||
验证码
|
||||
</text>
|
||||
<CodeInput v-model="code" />
|
||||
</view>
|
||||
|
||||
<text class="bind-phone-new__hint">
|
||||
{{ hint }}
|
||||
</text>
|
||||
|
||||
<view
|
||||
class="bind-phone-new__submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
验证并绑定手机号
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
87
src/pages/personal-center/bind-phone/index.scss
Normal file
87
src/pages/personal-center/bind-phone/index.scss
Normal file
@@ -0,0 +1,87 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.bind-phone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-phone__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.bind-phone__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-phone__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.bind-phone__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-phone__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.bind-phone__content {
|
||||
padding: 48rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bind-phone__field {
|
||||
width: 100%;
|
||||
margin-bottom: 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-phone__label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-phone__phone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
padding: 0 32rpx;
|
||||
box-sizing: border-box;
|
||||
background: $color-bg-input;
|
||||
border-radius: 48rpx;
|
||||
}
|
||||
|
||||
.bind-phone__phone-text {
|
||||
font-size: 30rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bind-phone__submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 48rpx;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
74
src/pages/personal-center/bind-phone/index.vue
Normal file
74
src/pages/personal-center/bind-phone/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { BOUND_PHONE_MASKED } from '@/constants/bind-phone'
|
||||
import CodeInput from '@/pages/Login/user-register/components/CodeInput.vue'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const boundPhone = BOUND_PHONE_MASKED
|
||||
const code = ref('')
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/account-security/index')
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!code.value.trim()) {
|
||||
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({ url: '/pages/personal-center/bind-phone-new/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bind-phone">
|
||||
<view class="bind-phone__navbar">
|
||||
<view
|
||||
class="bind-phone__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="bind-phone__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="bind-phone__navbar-title">
|
||||
绑定手机号
|
||||
</text>
|
||||
<view class="bind-phone__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="bind-phone__content">
|
||||
<view class="bind-phone__field">
|
||||
<text class="bind-phone__label">
|
||||
已绑定手机号
|
||||
</text>
|
||||
<view class="bind-phone__phone">
|
||||
<text class="bind-phone__phone-text">
|
||||
{{ boundPhone }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-phone__field">
|
||||
<text class="bind-phone__label">
|
||||
验证码
|
||||
</text>
|
||||
<CodeInput v-model="code" />
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="bind-phone__submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
验证并换绑
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
71
src/pages/personal-center/change-password/index.scss
Normal file
71
src/pages/personal-center/change-password/index.scss
Normal file
@@ -0,0 +1,71 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.change-password {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.change-password__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.change-password__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.change-password__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.change-password__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.change-password__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.change-password__content {
|
||||
padding: 48rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.change-password__field {
|
||||
width: 100%;
|
||||
margin-bottom: 24rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--password {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.change-password__strength {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.change-password__submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 48rpx;
|
||||
font-size: 30rpx;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
91
src/pages/personal-center/change-password/index.vue
Normal file
91
src/pages/personal-center/change-password/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import PasswordField from '@/pages/Login/user-register/components/PasswordField.vue'
|
||||
import PasswordStrength from '@/pages/Login/user-register/components/PasswordStrength.vue'
|
||||
import { getPasswordStrength } from '@/utils/password-strength'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
import { navigateToResultPage } from '@/utils/navigate-result'
|
||||
|
||||
const oldPassword = ref('')
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/account-security/index')
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!oldPassword.value || !newPassword.value || !confirmPassword.value) {
|
||||
uni.showToast({ title: '请填写完整信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
uni.showToast({ title: '两次密码输入不一致', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (getPasswordStrength(newPassword.value).isTooWeak) {
|
||||
uni.showToast({ title: '密码等级过低,请重新输入', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
navigateToResultPage('modifySuccess')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="change-password">
|
||||
<view class="change-password__navbar">
|
||||
<view
|
||||
class="change-password__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="change-password__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="change-password__navbar-title">
|
||||
修改密码
|
||||
</text>
|
||||
<view class="change-password__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="change-password__content">
|
||||
<view class="change-password__field">
|
||||
<PasswordField
|
||||
v-model="oldPassword"
|
||||
placeholder="输入旧密码"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="change-password__field change-password__field--password">
|
||||
<PasswordField
|
||||
v-model="newPassword"
|
||||
placeholder="设置新密码"
|
||||
/>
|
||||
<view class="change-password__strength">
|
||||
<PasswordStrength :password="newPassword" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="change-password__field">
|
||||
<PasswordField
|
||||
v-model="confirmPassword"
|
||||
placeholder="确认新密码"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="change-password__submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
确认修改
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
224
src/pages/personal-center/feedback/index.scss
Normal file
224
src/pages/personal-center/feedback/index.scss
Normal file
@@ -0,0 +1,224 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.feedback {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.feedback__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 20rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.feedback__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.feedback__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.feedback__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.feedback__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.feedback__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 20rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.feedback__card {
|
||||
padding: 28rpx 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.feedback__input {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
box-sizing: border-box;
|
||||
|
||||
&--title {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #e8eaef;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.feedback__placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.feedback__textarea-wrap {
|
||||
position: relative;
|
||||
margin-top: 24rpx;
|
||||
padding: 20rpx 24rpx 48rpx;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #e8eaef;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.feedback__textarea {
|
||||
width: 100%;
|
||||
min-height: 280rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.feedback__counter {
|
||||
position: absolute;
|
||||
right: 24rpx;
|
||||
bottom: 16rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.feedback__field {
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.feedback__label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.feedback__label {
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
|
||||
&--inline {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.feedback__hint {
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.feedback__attachment-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
padding: 10rpx 10rpx 0 0;
|
||||
}
|
||||
|
||||
.feedback__attachment-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 160rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
background: $color-bg-section;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.feedback__attachment-remove {
|
||||
position: absolute;
|
||||
top: -10rpx;
|
||||
right: -10rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.feedback__attachment-remove-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.feedback__attachment-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.feedback__attachment-name {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-primary;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.feedback__attachment-add {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
background: #f8f9fb;
|
||||
border: 1rpx dashed #d8dde6;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.feedback__attachment-add-icon {
|
||||
font-size: 40rpx;
|
||||
line-height: 1;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.feedback__attachment-add-text {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.feedback__footer {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.feedback__footer-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&--outline {
|
||||
flex: 0.8;
|
||||
color: $color-primary;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid $color-primary;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
flex: 1.2;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
}
|
||||
}
|
||||
173
src/pages/personal-center/feedback/index.vue
Normal file
173
src/pages/personal-center/feedback/index.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { FEEDBACK_DESCRIPTION_MAX_LENGTH } from '@/constants/feedback'
|
||||
import deleteIcon from '@/static/icons/X.png'
|
||||
import pdfIcon from '@/static/icons/PDF.png'
|
||||
import pngIcon from '@/static/icons/PNG.png'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const title = ref('')
|
||||
const description = ref('')
|
||||
|
||||
const attachments = ref([
|
||||
{ id: '1', name: '需求文档.pdf', icon: pdfIcon },
|
||||
{ id: '2', name: '系统....pdf', icon: pdfIcon },
|
||||
{ id: '3', name: '参考图.png', icon: pngIcon },
|
||||
])
|
||||
|
||||
const descLength = computed(() => description.value.length)
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleRemoveAttachment(id: string) {
|
||||
attachments.value = attachments.value.filter(item => item.id !== id)
|
||||
}
|
||||
|
||||
function handleAddAttachment() {
|
||||
uni.showToast({ title: '附件上传开发中', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
title.value = ''
|
||||
description.value = ''
|
||||
attachments.value = [
|
||||
{ id: '1', name: '需求文档.pdf', icon: pdfIcon },
|
||||
{ id: '2', name: '系统....pdf', icon: pdfIcon },
|
||||
{ id: '3', name: '参考图.png', icon: pngIcon },
|
||||
]
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!title.value.trim()) {
|
||||
uni.showToast({ title: '请输入反馈标题', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!description.value.trim()) {
|
||||
uni.showToast({ title: '请详细描述您的问题', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: '提交成功', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="feedback">
|
||||
<view class="feedback__navbar">
|
||||
<view
|
||||
class="feedback__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="feedback__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="feedback__navbar-title">
|
||||
意见反馈
|
||||
</text>
|
||||
<view class="feedback__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="feedback__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="feedback__card">
|
||||
<input
|
||||
v-model="title"
|
||||
class="feedback__input feedback__input--title"
|
||||
placeholder="请输入反馈标题"
|
||||
placeholder-class="feedback__placeholder"
|
||||
>
|
||||
|
||||
<view class="feedback__textarea-wrap">
|
||||
<textarea
|
||||
v-model="description"
|
||||
class="feedback__textarea"
|
||||
placeholder="请详细描述您的问题..."
|
||||
placeholder-class="feedback__placeholder"
|
||||
:maxlength="FEEDBACK_DESCRIPTION_MAX_LENGTH"
|
||||
/>
|
||||
<text class="feedback__counter">
|
||||
{{ descLength }}/{{ FEEDBACK_DESCRIPTION_MAX_LENGTH }}字
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="feedback__field">
|
||||
<view class="feedback__label-row">
|
||||
<text class="feedback__label feedback__label--inline">
|
||||
附件上传
|
||||
</text>
|
||||
<text class="feedback__hint">
|
||||
支持图片/文档/压缩包
|
||||
</text>
|
||||
</view>
|
||||
<view class="feedback__attachment-row">
|
||||
<view
|
||||
v-for="file in attachments"
|
||||
:key="file.id"
|
||||
class="feedback__attachment-item"
|
||||
>
|
||||
<view
|
||||
class="feedback__attachment-remove"
|
||||
@tap="handleRemoveAttachment(file.id)"
|
||||
>
|
||||
<image
|
||||
class="feedback__attachment-remove-icon"
|
||||
:src="deleteIcon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<image
|
||||
class="feedback__attachment-icon"
|
||||
:src="file.icon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="feedback__attachment-name">
|
||||
{{ file.name }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="feedback__attachment-add"
|
||||
@tap="handleAddAttachment"
|
||||
>
|
||||
<text class="feedback__attachment-add-icon">
|
||||
+
|
||||
</text>
|
||||
<text class="feedback__attachment-add-text">
|
||||
添加附件
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="feedback__footer">
|
||||
<view
|
||||
class="feedback__footer-btn feedback__footer-btn--primary"
|
||||
@tap="handleReset"
|
||||
>
|
||||
重置
|
||||
</view>
|
||||
<view
|
||||
class="feedback__footer-btn feedback__footer-btn--primary"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
提交反馈
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
154
src/pages/personal-center/finance/index.scss
Normal file
154
src/pages/personal-center/finance/index.scss
Normal file
@@ -0,0 +1,154 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.finance-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.finance-center__navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 20rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.finance-center__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.finance-center__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.finance-center__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.finance-center__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.finance-center__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 20rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.finance-center__summary {
|
||||
padding: 32rpx 28rpx 40rpx;
|
||||
background: linear-gradient(135deg, #3b7cff 0%, $color-home-header 100%);
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.finance-center__summary-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.finance-center__summary-label {
|
||||
font-size: 26rpx;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.finance-center__month {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 52rpx;
|
||||
padding: 0 20rpx;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 26rpx;
|
||||
}
|
||||
|
||||
.finance-center__month-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.finance-center__month-arrow {
|
||||
margin-left: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.finance-center__amount {
|
||||
display: block;
|
||||
margin-top: 28rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.finance-center__quick-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx 16rpx 32rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.finance-center__quick-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.finance-center__quick-item {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.finance-center__quick-icon-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
background: $color-bg-section;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.finance-center__quick-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
}
|
||||
|
||||
.finance-center__quick-label {
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.finance-center__chart-card {
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx 24rpx 32rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.finance-center__chart-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.finance-center__chart-placeholder {
|
||||
min-height: 360rpx;
|
||||
margin-top: 24rpx;
|
||||
background: $color-bg-section;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
122
src/pages/personal-center/finance/index.vue
Normal file
122
src/pages/personal-center/finance/index.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
FINANCE_CENTER_TOTAL,
|
||||
FINANCE_MONTH_OPTIONS,
|
||||
FINANCE_QUICK_ACTIONS,
|
||||
} from '@/constants/finance-center'
|
||||
import type { FinanceQuickAction } from '@/types/finance-center'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const monthOptions = FINANCE_MONTH_OPTIONS
|
||||
const quickActions = FINANCE_QUICK_ACTIONS
|
||||
const selectedMonthIndex = ref(0)
|
||||
|
||||
const selectedMonth = computed(() => monthOptions[selectedMonthIndex.value])
|
||||
|
||||
const totalAmountText = computed(() => `¥ ${FINANCE_CENTER_TOTAL}`)
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleSelectMonth() {
|
||||
uni.showActionSheet({
|
||||
itemList: monthOptions,
|
||||
success: (res) => {
|
||||
selectedMonthIndex.value = res.tapIndex
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleQuickAction(item: FinanceQuickAction) {
|
||||
if (item.path) {
|
||||
uni.navigateTo({ url: item.path })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: `${item.label}开发中`, icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="finance-center">
|
||||
<view class="finance-center__navbar">
|
||||
<view
|
||||
class="finance-center__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="finance-center__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="finance-center__navbar-title">
|
||||
财务管理
|
||||
</text>
|
||||
<view class="finance-center__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="finance-center__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="finance-center__summary">
|
||||
<view class="finance-center__summary-top">
|
||||
<text class="finance-center__summary-label">
|
||||
累计消费总额
|
||||
</text>
|
||||
<view
|
||||
class="finance-center__month"
|
||||
@tap="handleSelectMonth"
|
||||
>
|
||||
<text class="finance-center__month-text">
|
||||
{{ selectedMonth }}
|
||||
</text>
|
||||
<text class="finance-center__month-arrow">
|
||||
▾
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="finance-center__amount">
|
||||
{{ totalAmountText }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="finance-center__quick-card">
|
||||
<view class="finance-center__quick-grid">
|
||||
<view
|
||||
v-for="item in quickActions"
|
||||
:key="item.key"
|
||||
class="finance-center__quick-item"
|
||||
@tap="handleQuickAction(item)"
|
||||
>
|
||||
<view class="finance-center__quick-icon-wrap">
|
||||
<image
|
||||
class="finance-center__quick-icon"
|
||||
:src="item.icon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="finance-center__quick-label">
|
||||
{{ item.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="finance-center__chart-card">
|
||||
<text class="finance-center__chart-title">
|
||||
业务类型分布
|
||||
</text>
|
||||
<view class="finance-center__chart-placeholder" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -12,7 +12,7 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
padding: calc(var(--status-bar-height, 44px) + 12rpx) 24rpx 20rpx;
|
||||
padding: calc(var(--status-bar-height, 44px) + 60rpx) 24rpx 20rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
@@ -46,14 +46,21 @@
|
||||
}
|
||||
|
||||
.bill-detail__content {
|
||||
padding: 24rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
padding: 20rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bill-detail__hero {
|
||||
.bill-detail--with-footer .bill-detail__content {
|
||||
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.bill-detail__hero-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 24rpx 0 40rpx;
|
||||
padding: 40rpx 24rpx 36rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.bill-detail__icon {
|
||||
@@ -68,15 +75,26 @@
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bill-detail__amount {
|
||||
margin-top: 16rpx;
|
||||
.bill-detail__amount-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.bill-detail__amount-label {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.bill-detail__amount-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.bill-detail__card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
|
||||
@@ -146,3 +164,22 @@
|
||||
font-size: 22rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.bill-detail__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.bill-detail__pay-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getBill } from '@/constants/bill-list'
|
||||
import { PAYMENT_PLAN_STATUS_LABEL } from '@/constants/project-detail'
|
||||
import billPendingIcon from '@/static/icon/bill-pending.png'
|
||||
import notDueIcon from '@/static/icon/not-due.png'
|
||||
import successIcon from '@/static/icons/icon-register-success.png'
|
||||
import type { BillItem } from '@/types/bill'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
@@ -11,11 +14,57 @@ type PaymentMethod = 'wechat' | 'alipay'
|
||||
const bill = ref<BillItem | null>(null)
|
||||
const paymentMethod = ref<PaymentMethod>('wechat')
|
||||
|
||||
const paymentMethodLabel = computed(() =>
|
||||
paymentMethod.value === 'wechat' ? '微信支付' : '支付宝',
|
||||
const statusLabel = computed(() => {
|
||||
if (!bill.value)
|
||||
return ''
|
||||
return PAYMENT_PLAN_STATUS_LABEL[bill.value.status] || ''
|
||||
})
|
||||
|
||||
const heroIcon = computed(() => {
|
||||
if (bill.value?.status === 'paid')
|
||||
return successIcon
|
||||
if (bill.value?.status === 'not_due')
|
||||
return notDueIcon
|
||||
if (bill.value?.status === 'pending')
|
||||
return billPendingIcon
|
||||
return successIcon
|
||||
})
|
||||
|
||||
const paymentMethodLabel = computed(() => {
|
||||
if (!bill.value)
|
||||
return ''
|
||||
if (bill.value.status === 'paid')
|
||||
return paymentMethod.value === 'wechat' ? '微信支付' : '支付宝'
|
||||
if (bill.value.status === 'pending' || bill.value.status === 'not_due')
|
||||
return '微信支付'
|
||||
return '--'
|
||||
})
|
||||
|
||||
const paymentTime = computed(() => {
|
||||
if (!bill.value)
|
||||
return ''
|
||||
if (bill.value.paidAt)
|
||||
return bill.value.paidAt
|
||||
if (bill.value.status === 'pending' || bill.value.status === 'not_due')
|
||||
return '2026-01-21 15:00:00'
|
||||
return '--'
|
||||
})
|
||||
|
||||
const orderCardTitle = computed(() => {
|
||||
if (bill.value?.status === 'paid' || bill.value?.status === 'pending')
|
||||
return '已支付订单'
|
||||
if (bill.value?.status === 'not_due')
|
||||
return '账单订单'
|
||||
return '账单订单'
|
||||
})
|
||||
|
||||
const showPaymentInfo = computed(() =>
|
||||
bill.value?.status === 'paid'
|
||||
|| bill.value?.status === 'not_due'
|
||||
|| bill.value?.status === 'pending',
|
||||
)
|
||||
|
||||
const paymentTime = computed(() => bill.value?.paidAt || '2026-01-21 15:00:00')
|
||||
const showPayFooter = computed(() => bill.value?.status === 'pending')
|
||||
|
||||
onLoad((options) => {
|
||||
const projectId = options?.projectId ? decodeURIComponent(String(options.projectId)) : '1'
|
||||
@@ -37,12 +86,22 @@ function handleBack() {
|
||||
function formatAmount(amount: number) {
|
||||
return `¥ ${amount}`
|
||||
}
|
||||
|
||||
function handlePayNow() {
|
||||
if (!bill.value)
|
||||
return
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/project-center/payment/index?projectId=${encodeURIComponent(bill.value.projectId)}&planId=${encodeURIComponent(bill.value.id)}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
v-if="bill"
|
||||
class="bill-detail"
|
||||
:class="{ 'bill-detail--with-footer': showPayFooter }"
|
||||
>
|
||||
<view class="bill-detail__navbar">
|
||||
<view
|
||||
@@ -67,21 +126,31 @@ function formatAmount(amount: number) {
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="bill-detail__content">
|
||||
<view class="bill-detail__hero">
|
||||
<view class="bill-detail__hero-card">
|
||||
<image
|
||||
class="bill-detail__icon"
|
||||
:src="successIcon"
|
||||
:src="heroIcon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
|
||||
<text class="bill-detail__status">
|
||||
已支付
|
||||
{{ statusLabel }}
|
||||
</text>
|
||||
<text class="bill-detail__amount">
|
||||
付款金额:{{ formatAmount(bill.amount) }}
|
||||
|
||||
<view class="bill-detail__amount-row">
|
||||
<text class="bill-detail__amount-label">
|
||||
付款金额:
|
||||
</text>
|
||||
<text class="bill-detail__amount-value">
|
||||
{{ formatAmount(bill.amount) }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bill-detail__card">
|
||||
<view
|
||||
v-if="showPaymentInfo"
|
||||
class="bill-detail__card"
|
||||
>
|
||||
<text class="bill-detail__card-title">
|
||||
支付信息
|
||||
</text>
|
||||
@@ -113,7 +182,7 @@ function formatAmount(amount: number) {
|
||||
|
||||
<view class="bill-detail__card">
|
||||
<text class="bill-detail__card-title">
|
||||
已支付订单
|
||||
{{ orderCardTitle }}
|
||||
</text>
|
||||
<view class="bill-detail__order-row">
|
||||
<text class="bill-detail__order-title">
|
||||
@@ -132,6 +201,18 @@ function formatAmount(amount: number) {
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view
|
||||
v-if="showPayFooter"
|
||||
class="bill-detail__footer"
|
||||
>
|
||||
<view
|
||||
class="bill-detail__pay-btn"
|
||||
@tap="handlePayNow"
|
||||
>
|
||||
立即支付
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -132,16 +132,19 @@
|
||||
&--paid {
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
&--pending {
|
||||
color: #ffffff;
|
||||
background: #ff9a3d;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
|
||||
&--not_due {
|
||||
color: $color-primary;
|
||||
background: $color-primary-light;
|
||||
color: #ffffff;
|
||||
background: #25eb9f;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,16 +35,12 @@ function formatAmount(amount: number) {
|
||||
}
|
||||
|
||||
function handleBillTap(item: BillItem) {
|
||||
if (item.status === 'pending') {
|
||||
if (item.status === 'pending' || item.status === 'paid' || item.status === 'not_due') {
|
||||
const methodQuery = item.status === 'paid'
|
||||
? `&method=${encodeURIComponent(item.paidMethod || 'wechat')}`
|
||||
: ''
|
||||
uni.navigateTo({
|
||||
url: `/pages/project-center/payment/index?projectId=${encodeURIComponent(item.projectId)}&planId=${encodeURIComponent(item.id)}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (item.status === 'paid') {
|
||||
uni.navigateTo({
|
||||
url: `/pages/project-center/bill-detail/index?projectId=${encodeURIComponent(item.projectId)}&planId=${encodeURIComponent(item.id)}&method=${encodeURIComponent(item.paidMethod || 'wechat')}`,
|
||||
url: `/pages/project-center/bill-detail/index?projectId=${encodeURIComponent(item.projectId)}&planId=${encodeURIComponent(item.id)}${methodQuery}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
1
src/static/icon/bill-details.svg
Normal file
1
src/static/icon/bill-details.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1789454247895" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6431" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M790.66 460.5l40.69-34.96c9.43-8.1 10.5-22.3 2.4-31.73s-22.3-10.5-31.73-2.4l-39.08 33.57-41.03-33.84c-9.59-7.91-23.77-6.54-31.67 3.04-7.91 9.59-6.54 23.77 3.04 31.67l42 34.64h-54.64c-12.43 0-22.5 10.07-22.5 22.5s10.07 22.5 22.5 22.5h59v22h-59c-12.43 0-22.5 10.07-22.5 22.5s10.07 22.5 22.5 22.5h59V595c0 12.43 10.07 22.5 22.5 22.5s22.5-10.07 22.5-22.5v-22.5h59c12.43 0 22.5-10.07 22.5-22.5s-10.07-22.5-22.5-22.5h-59v-22h59c12.43 0 22.5-10.07 22.5-22.5s-10.07-22.5-22.5-22.5h-52.98z" p-id="6432" fill="#000000"></path><path d="M914.72 378.37c-32.34-43.18-79.16-71.8-131.84-80.59-53.47-8.92-106.43 3.59-149.14 35.24-42.16 31.23-69.16 77.39-76.05 129.97-6.88 52.58 7.18 105.37 39.59 148.65 32.34 43.18 79.16 71.8 131.84 80.59 11.26 1.88 22.48 2.81 33.6 2.81 41.7 0 81.82-13.06 115.54-38.04 42.16-31.23 69.16-77.39 76.05-129.97 6.88-52.6-7.18-105.39-39.59-148.66z m-19.91 140.85c-4.75 36.29-23.32 68.1-52.27 89.55-29.51 21.86-66.28 30.48-103.55 24.26-77.19-12.88-131.83-85.67-121.8-162.26 4.75-36.29 23.32-68.1 52.27-89.55 23.26-17.23 51.02-26.23 79.98-26.23 7.79 0 15.67 0.65 23.56 1.97 77.2 12.88 131.84 85.67 121.81 162.26zM643 267c0-19.33-15.67-35-35-35H289c-19.33 0-35 15.67-35 35s15.67 35 35 35h319c19.33 0 35-15.67 35-35zM460 413H289c-19.33 0-35 15.67-35 35s15.67 35 35 35h171c19.33 0 35-15.67 35-35s-15.67-35-35-35zM460 594H289c-19.33 0-35 15.67-35 35s15.67 35 35 35h171c19.33 0 35-15.67 35-35s-15.67-35-35-35z" p-id="6433" fill="#000000"></path><path d="M853.78 702.02c-22.42-0.43-40.78 18.35-40.78 40.78v93.91l-99.56-47.3c-20.38-9.68-44.02-10.29-64.87-1.68l-160.98 66.5a19.953 19.953 0 0 1-14.33 0.36L330.34 803.8a80.045 80.045 0 0 0-55.34 0.65l-87.14 33.29c-5.24 2-10.86-1.87-10.86-7.47V188c0-22.09 17.91-40 40-40h556c22.09 0 40 17.91 40 40v58.73c0 22.43 18.36 41.21 40.78 40.78 21.73-0.42 39.22-18.16 39.22-39.99V188c0-66.27-53.73-120-120-120H217c-66.27 0-120 53.73-120 120v652.86c0 25.05 11.23 48.99 31.05 64.31a79.992 79.992 0 0 0 77.5 11.45l91.1-34.81a19.94 19.94 0 0 1 13.83-0.16l143.21 50.89a79.943 79.943 0 0 0 26.79 4.63c10.38 0 20.75-2.03 30.54-6.07l159.87-66.04a19.974 19.974 0 0 1 16.22 0.42l91.55 43.49c41.69 19.8 94.24 1.65 113.85-51.86 0.32-0.88 0.48-1.82 0.48-2.75V742.01c0.01-21.83-17.48-39.58-39.21-39.99z" p-id="6434" fill="#000000"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src/static/icon/bill-pending.png
Normal file
BIN
src/static/icon/bill-pending.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
1
src/static/icon/invoice-apply.svg
Normal file
1
src/static/icon/invoice-apply.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1789454419489" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8796" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M810.666667 213.333333H213.333333c-25.6 0-42.666667 17.066667-42.666666 42.666667 0 21.333333 17.066667 38.4 38.4 42.666667H213.333333c0-25.6 17.066667-42.666667 42.666667-42.666667h512c25.6 0 42.666667 17.066667 42.666667 42.666667 25.6 0 42.666667-17.066667 42.666666-42.666667 0-21.333333-17.066667-38.4-38.4-42.666667H810.666667z m-149.333334 213.333334c12.8 0 21.333333 8.533333 21.333334 21.333333 0 8.533333-8.533333 17.066667-17.066667 21.333333H362.666667c-12.8 0-21.333333-8.533333-21.333334-21.333333 0-8.533333 8.533333-17.066667 17.066667-21.333333h302.933333z m0 128c12.8 0 21.333333 8.533333 21.333334 21.333333 0 8.533333-8.533333 17.066667-17.066667 21.333333H362.666667c-12.8 0-21.333333-8.533333-21.333334-21.333333 0-8.533333 8.533333-17.066667 17.066667-21.333333h302.933333zM768 298.666667H256v439.466666l4.266667-4.266666c17.066667-12.8 38.4-12.8 55.466666 0l4.266667 4.266666 42.666667 46.933334 42.666666-46.933334c17.066667-17.066667 42.666667-21.333333 59.733334-4.266666l4.266666 4.266666 42.666667 46.933334 42.666667-46.933334c17.066667-17.066667 42.666667-21.333333 59.733333-4.266666l4.266667 4.266666 42.666666 46.933334 42.666667-46.933334c17.066667-17.066667 42.666667-21.333333 59.733333-4.266666l4.266667 4.266666V298.666667z m42.666667-128c46.933333 0 85.333333 38.4 85.333333 85.333333s-38.4 85.333333-85.333333 85.333333v512l-76.8-85.333333-76.8 85.333333-76.8-85.333333-68.266667 85.333333-76.8-85.333333-76.8 85.333333-76.8-85.333333L213.333333 853.333333V341.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333s38.4-85.333333 85.333333-85.333333h597.333334z" p-id="8797" fill="#000000"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
BIN
src/static/icon/not-due.png
Normal file
BIN
src/static/icon/not-due.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
1
src/static/icon/pending-payment.svg
Normal file
1
src/static/icon/pending-payment.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1789454392241" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7511" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M853.3 668.3c-16.6 0-30 13.4-30 30v64.9c0 16.5-12 30-26.7 30h-612c-14.7 0-26.7-13.5-26.7-30V260.8c0-16.5 12-30 26.7-30h612c14.7 0 26.7 13.5 26.7 30v64.9c0 16.6 13.4 30 30 30s30-13.4 30-30v-64.9c0-49.6-38.9-90-86.7-90h-612c-47.8 0-86.7 40.4-86.7 90v502.4c0 49.6 38.9 90 86.7 90h612c47.8 0 86.7-40.4 86.7-90v-64.9c0-16.5-13.4-30-30-30zM552 512a40 40 0 1 0 80 0 40 40 0 1 0-80 0z m296.3-139.6H515.8c-49.6 0-90 40.4-90 90v99.1c0 49.6 40.4 90 90 90h332.5c49.6 0 90-40.4 90-90v-99.1c0-49.6-40.3-90-90-90z m30 189.1c0 16.5-13.5 30-30 30H515.8c-16.5 0-30-13.5-30-30v-99.1c0-16.5 13.5-30 30-30h332.5c16.5 0 30 13.5 30 30v99.1z" fill="#000000" p-id="7512"></path></svg>
|
||||
|
After Width: | Height: | Size: 992 B |
1
src/static/icon/report-export.svg
Normal file
1
src/static/icon/report-export.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1789454436192" class="icon" viewBox="0 0 1044 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9862" xmlns:xlink="http://www.w3.org/1999/xlink" width="203.90625" height="200"><path d="M206.848 1024H51.75A51.842 51.842 0 0 1 0 972.239V51.761A51.842 51.842 0 0 1 51.75 0h739.273a51.842 51.842 0 0 1 51.75 51.761v252.296a40.321 40.321 0 1 1-80.643 0V80.666H80.643v862.691h126.205a40.321 40.321 0 0 1 0 80.643z" p-id="9863"></path><path d="M587.447 253.967H187.172a40.321 40.321 0 1 1 0-80.643h400.275a40.321 40.321 0 0 1 0 80.643z m0 221.421H187.172a40.321 40.321 0 0 1 0-80.642h400.275a40.321 40.321 0 0 1 0 80.642zM318.71 696.798h-131.54a40.321 40.321 0 1 1 0-80.642h131.54a40.321 40.321 0 1 1 0 80.642z m0.277 325.958a45.586 45.586 0 0 1-45.414-49.93c3.86-39.745 19.39-142.023 80.228-233.057 35.103-52.498 79.906-93.441 133.06-121.69C539.66 590.04 600.765 574.59 668.966 572v-60.99a64.007 64.007 0 0 1 105.342-48.87l247.688 209.487a64.019 64.019 0 0 1 0 97.75L774.307 978.876a64.007 64.007 0 0 1-105.342-48.87v-89.594c-26.739-2.189-61.058-0.334-100.93 11.958-73.822 22.73-145.963 74.687-214.44 154.373a45.552 45.552 0 0 1-34.607 16.014z m330.427-263.817a306.73 306.73 0 0 1 68.95 7.65l31.243 7.212v120.387l205.327-173.635-205.327-173.646v107.14l-41.716-1.521c-205.558-7.316-292.33 113.717-328.906 215.615 53.109-44.238 108.418-75.343 165.317-92.866a356.383 356.383 0 0 1 105.112-16.278zM969.9 733.145h0.058z" p-id="9864"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
8
src/types/account-security.ts
Normal file
8
src/types/account-security.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type AccountSecurityItemKey = 'password' | 'phone' | 'email'
|
||||
|
||||
export interface AccountSecurityItem {
|
||||
key: AccountSecurityItemKey
|
||||
title: string
|
||||
subtitle: string
|
||||
status: string
|
||||
}
|
||||
8
src/types/finance-center.ts
Normal file
8
src/types/finance-center.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type FinanceQuickActionKey = 'bill-list' | 'pending' | 'invoice' | 'export'
|
||||
|
||||
export interface FinanceQuickAction {
|
||||
key: FinanceQuickActionKey
|
||||
label: string
|
||||
icon: string
|
||||
path?: string
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export type ResultPageType = 'register' | 'resetPassword' | 'loginFailed' | 'submitSuccess' | 'afterSalesSuccess'
|
||||
export type ResultPageType = 'register' | 'resetPassword' | 'loginFailed' | 'submitSuccess' | 'afterSalesSuccess' | 'modifySuccess'
|
||||
|
||||
export interface ResultPageConfig {
|
||||
title: string
|
||||
@@ -38,6 +38,12 @@ export const RESULT_PAGE_CONFIG: Record<ResultPageType, ResultPageConfig> = {
|
||||
icon: '/static/icons/icon-register-success.png',
|
||||
redirectUrl: '/pages/project-center/detail/index',
|
||||
},
|
||||
modifySuccess: {
|
||||
title: '修改成功',
|
||||
subtitleTarget: '',
|
||||
icon: '/static/icons/icon-register-success.png',
|
||||
redirectUrl: '/pages/personal-center/account-security/index',
|
||||
},
|
||||
}
|
||||
|
||||
export const RESULT_PAGE_ROUTE = '/pages/Login/user-register-success/index'
|
||||
|
||||
Reference in New Issue
Block a user