feat:项目中心和个人中心
This commit is contained in:
@@ -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">
|
||||
已支付
|
||||
</text>
|
||||
<text class="bill-detail__amount">
|
||||
付款金额:{{ formatAmount(bill.amount) }}
|
||||
{{ statusLabel }}
|
||||
</text>
|
||||
|
||||
<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}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user