feat:个人中心完成
This commit is contained in:
46
src/components/BottomSheet.scss
Normal file
46
src/components/BottomSheet.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.bottom-sheet {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 300;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.bottom-sheet__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.bottom-sheet__mask--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bottom-sheet__panel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: $color-bg-page;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
.bottom-sheet__panel--active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.bottom-sheet__body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
65
src/components/BottomSheet.vue
Normal file
65
src/components/BottomSheet.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const showSheet = ref(false)
|
||||
const sheetActive = ref(false)
|
||||
let closeTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (closeTimer) {
|
||||
clearTimeout(closeTimer)
|
||||
closeTimer = null
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
showSheet.value = true
|
||||
nextTick(() => {
|
||||
sheetActive.value = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sheetActive.value = false
|
||||
closeTimer = setTimeout(() => {
|
||||
showSheet.value = false
|
||||
}, 300)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
v-if="showSheet"
|
||||
class="bottom-sheet"
|
||||
>
|
||||
<view
|
||||
class="bottom-sheet__mask"
|
||||
:class="{ 'bottom-sheet__mask--active': sheetActive }"
|
||||
@tap="emit('close')"
|
||||
/>
|
||||
<view
|
||||
class="bottom-sheet__panel"
|
||||
:class="{ 'bottom-sheet__panel--active': sheetActive }"
|
||||
@tap.stop
|
||||
>
|
||||
<view class="bottom-sheet__body">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './BottomSheet.scss';
|
||||
</style>
|
||||
20
src/constants/address.ts
Normal file
20
src/constants/address.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AddressItem } from '@/types/address'
|
||||
|
||||
export const ADDRESS_LIST: AddressItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: '张三',
|
||||
phone: '15588888000',
|
||||
region: '山东省日照市东港区',
|
||||
detail: '科技园南路99号',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '张三',
|
||||
phone: '15588888000',
|
||||
region: '山东省日照市东港区',
|
||||
detail: '科技园南路99号',
|
||||
isDefault: false,
|
||||
},
|
||||
]
|
||||
53
src/constants/contract.ts
Normal file
53
src/constants/contract.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { ContractItem, ContractTab } from '@/types/contract'
|
||||
|
||||
export const CONTRACT_TABS: Array<{ value: ContractTab, label: string }> = [
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'pending', label: '待签署' },
|
||||
{ value: 'signed', label: '已签署' },
|
||||
]
|
||||
|
||||
export const CONTRACT_LIST: ContractItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
contractNo: 'C8K2M202408001',
|
||||
title: '社区康养智慧系统',
|
||||
type: '软件开发',
|
||||
relatedLabel: '关联合同',
|
||||
relatedValue: '20260814ZZZ151245',
|
||||
signStatus: 'signed',
|
||||
badges: [
|
||||
{ type: 'signed', label: '已签署' },
|
||||
{ type: 'in_progress', label: '履行中' },
|
||||
],
|
||||
showDownload: true,
|
||||
showSign: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
contractNo: 'C8K2M202408001',
|
||||
title: '社区康养智慧系统',
|
||||
type: '软件开发',
|
||||
relatedLabel: '关联项目',
|
||||
relatedValue: '社区康养智慧系统',
|
||||
signStatus: 'pending',
|
||||
badges: [
|
||||
{ type: 'pending_sign', label: '待签署' },
|
||||
],
|
||||
showDownload: false,
|
||||
showSign: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
contractNo: 'C8K2M202408001',
|
||||
title: '社区康养智慧系统',
|
||||
type: '软件开发',
|
||||
relatedLabel: '关联项目',
|
||||
relatedValue: '社区康养智慧系统',
|
||||
signStatus: 'signed',
|
||||
badges: [
|
||||
{ type: 'signed', label: '已签署' },
|
||||
],
|
||||
showDownload: false,
|
||||
showSign: false,
|
||||
},
|
||||
]
|
||||
31
src/constants/invoice-list.ts
Normal file
31
src/constants/invoice-list.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { InvoiceHeaderItem } from '@/types/invoice-list'
|
||||
|
||||
export const INVOICE_HEADER_LIST: InvoiceHeaderItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '山东日照有限公司',
|
||||
type: 'enterprise',
|
||||
taxNo: '344683434513543543545535',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '山东日照有限公司',
|
||||
type: 'personal',
|
||||
taxNo: '344683434513543543545535',
|
||||
isDefault: false,
|
||||
},
|
||||
]
|
||||
|
||||
export const INVOICE_HEADER_TYPE_LABEL: Record<InvoiceHeaderItem['type'], string> = {
|
||||
enterprise: '企业',
|
||||
personal: '个人',
|
||||
}
|
||||
|
||||
export const INVOICE_HEADER_FORM_TYPE_OPTIONS: Array<{
|
||||
value: InvoiceHeaderItem['type']
|
||||
label: string
|
||||
}> = [
|
||||
{ value: 'enterprise', label: '企业单位' },
|
||||
{ value: 'personal', label: '非企业单位' },
|
||||
]
|
||||
47
src/constants/invoice.ts
Normal file
47
src/constants/invoice.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { InvoiceRecordItem, InvoiceRecordStatus } from '@/types/invoice'
|
||||
|
||||
export const INVOICE_RECORD_TABS: Array<{ value: InvoiceRecordStatus, label: string }> = [
|
||||
{ value: 'all', label: '全部' },
|
||||
{ value: 'invoiced', label: '已开票' },
|
||||
{ value: 'not_invoiced', label: '未开票' },
|
||||
{ value: 'invoicing', label: '开票中' },
|
||||
]
|
||||
|
||||
export const INVOICE_RECORD_STATUS_LABEL: Record<InvoiceRecordItem['status'], string> = {
|
||||
invoiced: '已开票',
|
||||
not_invoiced: '未开票',
|
||||
invoicing: '开票中',
|
||||
}
|
||||
|
||||
export const INVOICE_RECORD_LIST: InvoiceRecordItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '第一期·预付款',
|
||||
ratio: '比例33%',
|
||||
amount: 20100,
|
||||
transactionNo: 'T20260819153035678K2M001',
|
||||
relatedProject: '社区康养智慧系统',
|
||||
invoiceTime: '2026-08-14 15:00',
|
||||
status: 'invoiced',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '第一期·预付款',
|
||||
ratio: '比例33%',
|
||||
amount: 20100,
|
||||
transactionNo: 'T20260819153035678K2M002',
|
||||
relatedProject: '社区康养智慧系统',
|
||||
invoiceTime: '2026-08-14 15:00',
|
||||
status: 'invoiced',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '第一期·预付款',
|
||||
ratio: '比例33%',
|
||||
amount: 20100,
|
||||
transactionNo: 'T20260819153035678K2M003',
|
||||
relatedProject: '社区康养智慧系统',
|
||||
invoiceTime: '2026-08-14 15:00',
|
||||
status: 'not_invoiced',
|
||||
},
|
||||
]
|
||||
@@ -11,7 +11,7 @@ import iconInvoice from '@/static/icon/invoice.png'
|
||||
import iconMessagePush from '@/static/icon/message-push.png'
|
||||
import iconQualification from '@/static/icon/qualification.png'
|
||||
import iconSecurity from '@/static/icon/security.png'
|
||||
import iconServiceHotline from '@/static/icon/service-hotline.png'
|
||||
import iconServiceHotline from '@/static/icon/service-hotline.svg'
|
||||
|
||||
export const PERSONAL_CENTER_PROFILE: PersonalCenterProfile = {
|
||||
nickname: '这里是昵称',
|
||||
@@ -23,15 +23,17 @@ export const PERSONAL_CENTER_MENU: PersonalCenterMenuItem[] = [
|
||||
{ key: 'security', label: '账号安全', icon: iconSecurity, path: '/pages/personal-center/account-security/index' },
|
||||
{ key: 'message-push', label: '消息推送', icon: iconMessagePush },
|
||||
{ key: 'feedback', label: '意见反馈', icon: iconFeedback, path: '/pages/personal-center/feedback/index' },
|
||||
{ key: 'contract-list', label: '合同列表', icon: iconContractList },
|
||||
{ key: 'contract-list', label: '合同列表', icon: iconContractList, path: '/pages/contract-center/index' },
|
||||
{ key: 'finance', label: '财务中心', icon: iconFinance, path: '/pages/personal-center/finance/index' },
|
||||
{ key: 'invoice', label: '发票列表', icon: iconInvoice },
|
||||
{ key: 'address', label: '地址管理', icon: iconAddress },
|
||||
{ key: 'invoice', label: '发票列表', icon: iconInvoice, path: '/pages/personal-center/invoice/index' },
|
||||
{ key: 'address', label: '地址管理', icon: iconAddress, path: '/pages/personal-center/address/index' },
|
||||
{ key: 'favorites', label: '收藏列表', icon: iconFavorites, path: '/pages/case-library/favorites/index' },
|
||||
{ key: 'qualification', label: '资质列表', icon: iconQualification },
|
||||
{ key: 'qualification', label: '资质列表', icon: iconQualification, path: '/pages/personal-center/qualification-list/index' },
|
||||
{ key: 'help', label: '帮助中心', icon: iconHelp },
|
||||
{ key: 'about', label: '关于我们', icon: iconAbout },
|
||||
{ key: 'service-hotline', label: '专属客服热线', icon: iconServiceHotline },
|
||||
{ key: 'service-hotline', label: '专属客服热线', icon: iconServiceHotline, path: '/pages/personal-center/service-hotline/index' },
|
||||
]
|
||||
|
||||
export const PERSONAL_CENTER_SERVICE_HOTLINE = '400-000-0000'
|
||||
|
||||
export const PERSONAL_CENTER_SERVICE_HOTLINE_HOURS = '工作日 8:00-12:00 14:00-18:00'
|
||||
|
||||
38
src/constants/qualification.ts
Normal file
38
src/constants/qualification.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { QualificationItem, QualificationStatus } from '@/types/qualification'
|
||||
|
||||
export const QUALIFICATION_TYPE_OPTIONS = [
|
||||
'营业执照',
|
||||
'高新技术企业证书',
|
||||
'ISO 质量管理体系认证',
|
||||
'软件企业认定证书',
|
||||
]
|
||||
|
||||
export const QUALIFICATION_STATUS_LABEL: Record<QualificationStatus, string> = {
|
||||
reviewing: '审核中',
|
||||
approved: '已通过',
|
||||
rejected: '已驳回',
|
||||
draft: '草稿',
|
||||
}
|
||||
|
||||
export const QUALIFICATION_LIST: QualificationItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: '营业执照',
|
||||
certificateName: '山东日照有限公司',
|
||||
certificateNo: '344683434513543543545535',
|
||||
issuer: '山东省政务局',
|
||||
validFrom: '2024-10-10',
|
||||
validTo: '2027-10-10',
|
||||
status: 'reviewing',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: '营业执照',
|
||||
certificateName: '山东日照有限公司',
|
||||
certificateNo: '344683434513543543545535',
|
||||
issuer: '山东省政务局',
|
||||
validFrom: '2024-10-10',
|
||||
validTo: '2027-10-10',
|
||||
status: 'reviewing',
|
||||
},
|
||||
]
|
||||
16
src/hooks/use-invoice-headers.ts
Normal file
16
src/hooks/use-invoice-headers.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { computed, ref } from 'vue'
|
||||
import { INVOICE_HEADER_LIST } from '@/constants/invoice-list'
|
||||
import type { InvoiceHeaderItem } from '@/types/invoice-list'
|
||||
|
||||
const invoiceHeaders = ref<InvoiceHeaderItem[]>(INVOICE_HEADER_LIST.map(item => ({ ...item })))
|
||||
|
||||
export function useInvoiceHeaders() {
|
||||
const defaultHeader = computed(() =>
|
||||
invoiceHeaders.value.find(item => item.isDefault) || invoiceHeaders.value[0] || null,
|
||||
)
|
||||
|
||||
return {
|
||||
invoiceHeaders,
|
||||
defaultHeader,
|
||||
}
|
||||
}
|
||||
16
src/hooks/use-qualifications.ts
Normal file
16
src/hooks/use-qualifications.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ref } from 'vue'
|
||||
import { QUALIFICATION_LIST } from '@/constants/qualification'
|
||||
import type { QualificationItem } from '@/types/qualification'
|
||||
|
||||
const qualifications = ref<QualificationItem[]>(QUALIFICATION_LIST.map(item => ({ ...item })))
|
||||
|
||||
export function useQualifications() {
|
||||
function getQualificationById(id: string) {
|
||||
return qualifications.value.find(item => item.id === id) || null
|
||||
}
|
||||
|
||||
return {
|
||||
qualifications,
|
||||
getQualificationById,
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,48 @@
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/invoice/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/invoice-list/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/address/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/contract-center/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/service-hotline/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/qualification-list/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/personal-center/qualification-upload/index",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
229
src/pages/contract-center/index.scss
Normal file
229
src/pages/contract-center/index.scss
Normal file
@@ -0,0 +1,229 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.contract-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.contract-center__header {
|
||||
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;
|
||||
}
|
||||
|
||||
.contract-center__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.contract-center__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.contract-center__title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.contract-center__header-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.contract-center__toolbar {
|
||||
flex-shrink: 0;
|
||||
padding: 0 24rpx 24rpx;
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.contract-center__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 72rpx;
|
||||
padding: 0 24rpx;
|
||||
background: $color-bg-section;
|
||||
border-radius: 36rpx;
|
||||
}
|
||||
|
||||
.contract-center__search-icon {
|
||||
flex-shrink: 0;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.contract-center__search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.contract-center__search-placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.contract-center__tabs {
|
||||
display: flex;
|
||||
margin-top: 24rpx;
|
||||
overflow: hidden;
|
||||
border: 2rpx solid $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.contract-center__tab {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 72rpx;
|
||||
background: $color-bg-page;
|
||||
border-right: 2rpx solid $color-primary;
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.contract-center__tab-text {
|
||||
font-size: 28rpx;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.contract-center__tab--active .contract-center__tab-text {
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.contract-center__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.contract-center__list {
|
||||
padding: 0 24rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.contract-center__card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-top: 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.contract-center__card-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.contract-center__contract-no {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.contract-center__badges {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.contract-center__badge {
|
||||
padding: 4rpx 12rpx;
|
||||
font-size: 20rpx;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&--signed {
|
||||
color: #16a34a;
|
||||
background: #ecfdf3;
|
||||
}
|
||||
|
||||
&--in_progress {
|
||||
color: $color-primary;
|
||||
background: $color-primary-light;
|
||||
}
|
||||
|
||||
&--pending_sign {
|
||||
color: $color-stat-orange;
|
||||
background: $color-stat-orange-bg;
|
||||
}
|
||||
}
|
||||
|
||||
.contract-center__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.contract-center__card-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.contract-center__detail-link {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.contract-center__related {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.contract-center__actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 28rpx;
|
||||
|
||||
&--single {
|
||||
.contract-center__action-btn {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contract-center__action-btn {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 12rpx;
|
||||
|
||||
&--outline {
|
||||
color: $color-text-primary;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #d8dde6;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
}
|
||||
}
|
||||
178
src/pages/contract-center/index.vue
Normal file
178
src/pages/contract-center/index.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { CONTRACT_LIST, CONTRACT_TABS } from '@/constants/contract'
|
||||
import type { ContractItem, ContractTab } from '@/types/contract'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const keyword = ref('')
|
||||
const activeTab = ref<ContractTab>('all')
|
||||
const contractList = ref(CONTRACT_LIST.map(item => ({ ...item })))
|
||||
|
||||
const filteredContracts = computed(() => {
|
||||
const text = keyword.value.trim()
|
||||
|
||||
return contractList.value.filter((item) => {
|
||||
const matchTab = activeTab.value === 'all'
|
||||
|| (activeTab.value === 'pending' && item.signStatus === 'pending')
|
||||
|| (activeTab.value === 'signed' && item.signStatus === 'signed')
|
||||
const matchKeyword = !text
|
||||
|| item.title.includes(text)
|
||||
|| item.type.includes(text)
|
||||
|| item.contractNo.includes(text)
|
||||
|| item.relatedValue.includes(text)
|
||||
|
||||
return matchTab && matchKeyword
|
||||
})
|
||||
})
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function badgeClass(type: ContractItem['badges'][number]['type']) {
|
||||
return `contract-center__badge--${type}`
|
||||
}
|
||||
|
||||
function handleViewDetail(item: ContractItem) {
|
||||
uni.showToast({ title: `查看${item.title}详情开发中`, icon: 'none' })
|
||||
}
|
||||
|
||||
function handlePreview(item: ContractItem) {
|
||||
uni.showToast({ title: `预览${item.contractNo}开发中`, icon: 'none' })
|
||||
}
|
||||
|
||||
function handleDownload(item: ContractItem) {
|
||||
uni.showToast({ title: `下载${item.contractNo}开发中`, icon: 'none' })
|
||||
}
|
||||
|
||||
function handleSign(item: ContractItem) {
|
||||
uni.showToast({ title: `签署${item.contractNo}开发中`, icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="contract-center">
|
||||
<view class="contract-center__header">
|
||||
<view
|
||||
class="contract-center__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="contract-center__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="contract-center__title">
|
||||
合同中心
|
||||
</text>
|
||||
<view class="contract-center__header-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="contract-center__toolbar">
|
||||
<view class="contract-center__search">
|
||||
<image
|
||||
class="contract-center__search-icon"
|
||||
src="/static/icons/icon-search.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<input
|
||||
v-model="keyword"
|
||||
class="contract-center__search-input"
|
||||
placeholder="搜索项目标题、类型..."
|
||||
placeholder-class="contract-center__search-placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="contract-center__tabs">
|
||||
<view
|
||||
v-for="tab in CONTRACT_TABS"
|
||||
:key="tab.value"
|
||||
class="contract-center__tab"
|
||||
:class="{ 'contract-center__tab--active': activeTab === tab.value }"
|
||||
@tap="activeTab = tab.value"
|
||||
>
|
||||
<text class="contract-center__tab-text">
|
||||
{{ tab.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="contract-center__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="contract-center__list">
|
||||
<view
|
||||
v-for="item in filteredContracts"
|
||||
:key="item.id"
|
||||
class="contract-center__card"
|
||||
>
|
||||
<view class="contract-center__card-top">
|
||||
<text class="contract-center__contract-no">
|
||||
合同编号: {{ item.contractNo }}
|
||||
</text>
|
||||
<view class="contract-center__badges">
|
||||
<text
|
||||
v-for="badge in item.badges"
|
||||
:key="badge.label"
|
||||
class="contract-center__badge"
|
||||
:class="badgeClass(badge.type)"
|
||||
>
|
||||
{{ badge.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="contract-center__title-row">
|
||||
<text class="contract-center__card-title">
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<text
|
||||
class="contract-center__detail-link"
|
||||
@tap="handleViewDetail(item)"
|
||||
>
|
||||
查看详情
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<text class="contract-center__related">
|
||||
{{ item.relatedLabel }}: {{ item.relatedValue }}
|
||||
</text>
|
||||
|
||||
<view
|
||||
class="contract-center__actions"
|
||||
:class="{ 'contract-center__actions--single': !item.showDownload && !item.showSign }"
|
||||
>
|
||||
<view
|
||||
class="contract-center__action-btn contract-center__action-btn--outline"
|
||||
@tap="handlePreview(item)"
|
||||
>
|
||||
预览
|
||||
</view>
|
||||
<view
|
||||
v-if="item.showDownload"
|
||||
class="contract-center__action-btn contract-center__action-btn--outline"
|
||||
@tap="handleDownload(item)"
|
||||
>
|
||||
下载
|
||||
</view>
|
||||
<view
|
||||
v-if="item.showSign"
|
||||
class="contract-center__action-btn contract-center__action-btn--primary"
|
||||
@tap="handleSign(item)"
|
||||
>
|
||||
立即签署
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -2,7 +2,8 @@
|
||||
import AppTabBar from '@/components/AppTabBar.vue'
|
||||
import { useGreeting } from '@/hooks/use-greeting'
|
||||
import { useQuickEntries } from '@/hooks/use-quick-entries'
|
||||
import type { ProjectItem, ServiceItem } from '@/types/home'
|
||||
import type { ProjectItem, QuickEntryItem, ServiceItem } from '@/types/home'
|
||||
import { navigateQuickEntry } from '@/utils/quick-entry-navigate'
|
||||
import headerBg from '@/static/icons/1.png'
|
||||
import softwareIllus from '@/static/icons/software.png'
|
||||
import hardwareIllus from '@/static/icons/hardware.png'
|
||||
@@ -56,6 +57,10 @@ const services: ServiceItem[] = [
|
||||
},
|
||||
]
|
||||
|
||||
function handleQuickEntryTap(entry: QuickEntryItem) {
|
||||
navigateQuickEntry(entry.id, entry.label)
|
||||
}
|
||||
|
||||
const projects: ProjectItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
@@ -163,6 +168,7 @@ const projects: ProjectItem[] = [
|
||||
v-for="entry in pinnedEntries"
|
||||
:key="entry.id"
|
||||
class="home__quick-item"
|
||||
@tap="handleQuickEntryTap(entry)"
|
||||
>
|
||||
<image
|
||||
class="home__quick-icon"
|
||||
|
||||
209
src/pages/personal-center/address/index.scss
Normal file
209
src/pages/personal-center/address/index.scss
Normal file
@@ -0,0 +1,209 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.address-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.address-page__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;
|
||||
}
|
||||
|
||||
.address-page__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.address-page__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.address-page__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.address-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.address-page__content {
|
||||
padding: 20rpx 24rpx calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.address-page__card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.address-page__card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.address-page__name {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__phone {
|
||||
margin-left: 24rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__address {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.5;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.address-page__card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.address-page__default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.address-page__radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border: 2rpx solid #d8dde6;
|
||||
border-radius: 50%;
|
||||
|
||||
&--checked {
|
||||
border-color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.address-page__radio-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background: $color-primary;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.address-page__default-text {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.address-page__actions {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.address-page__action {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.address-page__add-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;
|
||||
}
|
||||
|
||||
.address-page__sheet-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__form {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.address-page__form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 96rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&--last {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.address-page__form-label {
|
||||
flex-shrink: 0;
|
||||
width: 160rpx;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.address-page__form-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.address-page__form-placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.address-page__sheet-submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 40rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
284
src/pages/personal-center/address/index.vue
Normal file
284
src/pages/personal-center/address/index.vue
Normal file
@@ -0,0 +1,284 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import BottomSheet from '@/components/BottomSheet.vue'
|
||||
import { ADDRESS_LIST } from '@/constants/address'
|
||||
import type { AddressItem } from '@/types/address'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const addressList = ref(ADDRESS_LIST.map(item => ({ ...item })))
|
||||
const sheetVisible = ref(false)
|
||||
const editingId = ref<string | null>(null)
|
||||
const formName = ref('')
|
||||
const formPhone = ref('')
|
||||
const formRegion = ref('')
|
||||
const formDetail = ref('')
|
||||
|
||||
const isEditing = computed(() => editingId.value !== null)
|
||||
const sheetTitle = computed(() => (isEditing.value ? '编辑地址' : '新增地址'))
|
||||
const sheetSubmitText = computed(() => (isEditing.value ? '保存' : '+ 新增地址'))
|
||||
|
||||
function formatAddress(item: AddressItem) {
|
||||
return `${item.region}${item.detail}`
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleSetDefault(item: AddressItem) {
|
||||
addressList.value = addressList.value.map(row => ({
|
||||
...row,
|
||||
isDefault: row.id === item.id,
|
||||
}))
|
||||
}
|
||||
|
||||
function handleEdit(item: AddressItem) {
|
||||
editingId.value = item.id
|
||||
formName.value = item.name
|
||||
formPhone.value = item.phone
|
||||
formRegion.value = item.region
|
||||
formDetail.value = item.detail
|
||||
sheetVisible.value = true
|
||||
}
|
||||
|
||||
function handleDelete(item: AddressItem) {
|
||||
uni.showModal({
|
||||
title: '删除地址',
|
||||
content: `确认删除「${item.name}」的地址吗?`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#2563EB',
|
||||
success: (res) => {
|
||||
if (!res.confirm)
|
||||
return
|
||||
|
||||
addressList.value = addressList.value.filter(row => row.id !== item.id)
|
||||
if (addressList.value.length && !addressList.value.some(row => row.isDefault))
|
||||
addressList.value[0].isDefault = true
|
||||
|
||||
uni.showToast({ title: '已删除', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
editingId.value = null
|
||||
formName.value = ''
|
||||
formPhone.value = ''
|
||||
formRegion.value = ''
|
||||
formDetail.value = ''
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
resetForm()
|
||||
sheetVisible.value = true
|
||||
}
|
||||
|
||||
function closeSheet() {
|
||||
sheetVisible.value = false
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!formName.value.trim()) {
|
||||
uni.showToast({ title: '请输入收货人姓名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!formPhone.value.trim()) {
|
||||
uni.showToast({ title: '请输入联系电话', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!formRegion.value.trim()) {
|
||||
uni.showToast({ title: '请输入所在地区', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!formDetail.value.trim()) {
|
||||
uni.showToast({ title: '请输入详细地址', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name: formName.value.trim(),
|
||||
phone: formPhone.value.trim(),
|
||||
region: formRegion.value.trim(),
|
||||
detail: formDetail.value.trim(),
|
||||
}
|
||||
|
||||
if (editingId.value) {
|
||||
addressList.value = addressList.value.map(row => (
|
||||
row.id === editingId.value
|
||||
? { ...row, ...payload }
|
||||
: row
|
||||
))
|
||||
closeSheet()
|
||||
uni.showToast({ title: '保存成功', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const isFirst = addressList.value.length === 0
|
||||
addressList.value.push({
|
||||
id: `${Date.now()}`,
|
||||
...payload,
|
||||
isDefault: isFirst,
|
||||
})
|
||||
|
||||
closeSheet()
|
||||
uni.showToast({ title: '新增成功', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="address-page">
|
||||
<view class="address-page__navbar">
|
||||
<view
|
||||
class="address-page__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="address-page__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="address-page__navbar-title">
|
||||
地址管理
|
||||
</text>
|
||||
<view class="address-page__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="address-page__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="address-page__content">
|
||||
<view
|
||||
v-for="item in addressList"
|
||||
:key="item.id"
|
||||
class="address-page__card"
|
||||
>
|
||||
<view class="address-page__card-top">
|
||||
<text class="address-page__name">
|
||||
{{ item.name }}
|
||||
</text>
|
||||
<text class="address-page__phone">
|
||||
{{ item.phone }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="address-page__address">
|
||||
{{ formatAddress(item) }}
|
||||
</text>
|
||||
<view class="address-page__card-footer">
|
||||
<view
|
||||
class="address-page__default"
|
||||
@tap="handleSetDefault(item)"
|
||||
>
|
||||
<view
|
||||
class="address-page__radio"
|
||||
:class="{ 'address-page__radio--checked': item.isDefault }"
|
||||
>
|
||||
<view
|
||||
v-if="item.isDefault"
|
||||
class="address-page__radio-dot"
|
||||
/>
|
||||
</view>
|
||||
<text class="address-page__default-text">
|
||||
默认地址
|
||||
</text>
|
||||
</view>
|
||||
<view class="address-page__actions">
|
||||
<text
|
||||
class="address-page__action"
|
||||
@tap="handleEdit(item)"
|
||||
>
|
||||
编辑
|
||||
</text>
|
||||
<text
|
||||
class="address-page__action"
|
||||
@tap="handleDelete(item)"
|
||||
>
|
||||
删除
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="address-page__footer">
|
||||
<view
|
||||
class="address-page__add-btn"
|
||||
@tap="handleAdd"
|
||||
>
|
||||
+ 新增地址
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<BottomSheet
|
||||
:visible="sheetVisible"
|
||||
@close="closeSheet"
|
||||
>
|
||||
<text class="address-page__sheet-title">
|
||||
{{ sheetTitle }}
|
||||
</text>
|
||||
|
||||
<view class="address-page__form">
|
||||
<view class="address-page__form-row">
|
||||
<text class="address-page__form-label">
|
||||
收货人
|
||||
</text>
|
||||
<input
|
||||
v-model="formName"
|
||||
class="address-page__form-input"
|
||||
placeholder="请输入收货人姓名"
|
||||
placeholder-class="address-page__form-placeholder"
|
||||
>
|
||||
</view>
|
||||
<view class="address-page__form-row">
|
||||
<text class="address-page__form-label">
|
||||
手机号码
|
||||
</text>
|
||||
<input
|
||||
v-model="formPhone"
|
||||
class="address-page__form-input"
|
||||
type="number"
|
||||
placeholder="请输入联系电话"
|
||||
placeholder-class="address-page__form-placeholder"
|
||||
>
|
||||
</view>
|
||||
<view class="address-page__form-row">
|
||||
<text class="address-page__form-label">
|
||||
地区
|
||||
</text>
|
||||
<input
|
||||
v-model="formRegion"
|
||||
class="address-page__form-input"
|
||||
placeholder="请输入所在地区"
|
||||
placeholder-class="address-page__form-placeholder"
|
||||
>
|
||||
</view>
|
||||
<view class="address-page__form-row address-page__form-row--last">
|
||||
<text class="address-page__form-label">
|
||||
详细地址
|
||||
</text>
|
||||
<input
|
||||
v-model="formDetail"
|
||||
class="address-page__form-input"
|
||||
placeholder="请输入街道、小区门牌等详细地址"
|
||||
placeholder-class="address-page__form-placeholder"
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="address-page__sheet-submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
{{ sheetSubmitText }}
|
||||
</view>
|
||||
</BottomSheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
@@ -3,7 +3,6 @@ import AppTabBar from '@/components/AppTabBar.vue'
|
||||
import {
|
||||
PERSONAL_CENTER_MENU,
|
||||
PERSONAL_CENTER_PROFILE,
|
||||
PERSONAL_CENTER_SERVICE_HOTLINE,
|
||||
} from '@/constants/personal-center'
|
||||
import type { PersonalCenterMenuItem } from '@/types/personal-center'
|
||||
import editIcon from '@/static/icon/edit.svg'
|
||||
@@ -21,16 +20,6 @@ function handleMenuTap(item: PersonalCenterMenuItem) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item.key === 'service-hotline') {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: PERSONAL_CENTER_SERVICE_HOTLINE.replace(/-/g, ''),
|
||||
fail: () => {
|
||||
uni.showToast({ title: PERSONAL_CENTER_SERVICE_HOTLINE, icon: 'none' })
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
254
src/pages/personal-center/invoice-list/index.scss
Normal file
254
src/pages/personal-center/invoice-list/index.scss
Normal file
@@ -0,0 +1,254 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.invoice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.invoice-list__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;
|
||||
}
|
||||
|
||||
.invoice-list__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.invoice-list__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.invoice-list__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.invoice-list__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.invoice-list__content {
|
||||
padding: 20rpx 24rpx calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.invoice-list__card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-list__card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invoice-list__card-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__tag {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
font-size: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&--enterprise {
|
||||
color: $color-primary;
|
||||
border: 1rpx solid $color-primary;
|
||||
}
|
||||
|
||||
&--personal {
|
||||
color: $color-stat-orange;
|
||||
border: 1rpx solid $color-stat-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-list__tax-no {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #222222;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.invoice-list__card-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.invoice-list__default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invoice-list__radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border: 2rpx solid #d8dde6;
|
||||
border-radius: 50%;
|
||||
|
||||
&--checked {
|
||||
border-color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-list__radio-dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
background: $color-primary;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.invoice-list__default-text {
|
||||
margin-left: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.invoice-list__actions {
|
||||
display: flex;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.invoice-list__action {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.invoice-list__add-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;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-field {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__type-row {
|
||||
display: flex;
|
||||
gap: 48rpx;
|
||||
}
|
||||
|
||||
.invoice-list__type-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invoice-list__type-radio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border: 2rpx solid #d8dde6;
|
||||
border-radius: 50%;
|
||||
|
||||
&--checked {
|
||||
background: $color-primary;
|
||||
border-color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-list__type-check {
|
||||
font-size: 20rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.invoice-list__type-label {
|
||||
margin-left: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #e8eaef;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.invoice-list__sheet-submit {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 40rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
290
src/pages/personal-center/invoice-list/index.vue
Normal file
290
src/pages/personal-center/invoice-list/index.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import BottomSheet from '@/components/BottomSheet.vue'
|
||||
import {
|
||||
INVOICE_HEADER_FORM_TYPE_OPTIONS,
|
||||
INVOICE_HEADER_TYPE_LABEL,
|
||||
} from '@/constants/invoice-list'
|
||||
import { useInvoiceHeaders } from '@/hooks/use-invoice-headers'
|
||||
import type { InvoiceHeaderItem } from '@/types/invoice-list'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const { invoiceHeaders: invoiceList } = useInvoiceHeaders()
|
||||
const sheetVisible = ref(false)
|
||||
const editingId = ref<string | null>(null)
|
||||
const headerType = ref<InvoiceHeaderItem['type']>('enterprise')
|
||||
const headerTitle = ref('')
|
||||
const taxNo = ref('')
|
||||
|
||||
const isEditing = computed(() => editingId.value !== null)
|
||||
const sheetTitle = computed(() => (isEditing.value ? '编辑抬头' : '新增抬头'))
|
||||
const sheetSubmitText = computed(() => (isEditing.value ? '保存' : '+ 新增抬头'))
|
||||
|
||||
const formTypeOptions = INVOICE_HEADER_FORM_TYPE_OPTIONS
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/invoice/index')
|
||||
}
|
||||
|
||||
function typeLabel(type: InvoiceHeaderItem['type']) {
|
||||
return INVOICE_HEADER_TYPE_LABEL[type]
|
||||
}
|
||||
|
||||
function handleSetDefault(item: InvoiceHeaderItem) {
|
||||
invoiceList.value = invoiceList.value.map(row => ({
|
||||
...row,
|
||||
isDefault: row.id === item.id,
|
||||
}))
|
||||
}
|
||||
|
||||
function handleEdit(item: InvoiceHeaderItem) {
|
||||
editingId.value = item.id
|
||||
headerType.value = item.type
|
||||
headerTitle.value = item.title
|
||||
taxNo.value = item.taxNo
|
||||
sheetVisible.value = true
|
||||
}
|
||||
|
||||
function handleDelete(item: InvoiceHeaderItem) {
|
||||
uni.showModal({
|
||||
title: '删除抬头',
|
||||
content: `确认删除「${item.title}」吗?`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#2563EB',
|
||||
success: (res) => {
|
||||
if (!res.confirm)
|
||||
return
|
||||
|
||||
invoiceList.value = invoiceList.value.filter(row => row.id !== item.id)
|
||||
if (invoiceList.value.length && !invoiceList.value.some(row => row.isDefault))
|
||||
invoiceList.value[0].isDefault = true
|
||||
|
||||
uni.showToast({ title: '已删除', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
editingId.value = null
|
||||
headerType.value = 'enterprise'
|
||||
headerTitle.value = ''
|
||||
taxNo.value = ''
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
resetForm()
|
||||
sheetVisible.value = true
|
||||
}
|
||||
|
||||
function closeSheet() {
|
||||
sheetVisible.value = false
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
function handleSelectType(type: InvoiceHeaderItem['type']) {
|
||||
headerType.value = type
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!headerTitle.value.trim()) {
|
||||
uni.showToast({ title: '请填写发票抬头', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!taxNo.value.trim()) {
|
||||
uni.showToast({ title: '请输入税号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title: headerTitle.value.trim(),
|
||||
type: headerType.value,
|
||||
taxNo: taxNo.value.trim(),
|
||||
}
|
||||
|
||||
if (editingId.value) {
|
||||
invoiceList.value = invoiceList.value.map(row => (
|
||||
row.id === editingId.value
|
||||
? { ...row, ...payload }
|
||||
: row
|
||||
))
|
||||
closeSheet()
|
||||
uni.showToast({ title: '保存成功', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const isFirst = invoiceList.value.length === 0
|
||||
invoiceList.value.push({
|
||||
id: `${Date.now()}`,
|
||||
...payload,
|
||||
isDefault: isFirst,
|
||||
})
|
||||
|
||||
closeSheet()
|
||||
uni.showToast({ title: '新增成功', icon: 'none' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="invoice-list">
|
||||
<view class="invoice-list__navbar">
|
||||
<view
|
||||
class="invoice-list__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="invoice-list__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="invoice-list__navbar-title">
|
||||
抬头管理
|
||||
</text>
|
||||
<view class="invoice-list__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="invoice-list__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="invoice-list__content">
|
||||
<view
|
||||
v-for="item in invoiceList"
|
||||
:key="item.id"
|
||||
class="invoice-list__card"
|
||||
>
|
||||
<view class="invoice-list__card-top">
|
||||
<text class="invoice-list__card-title">
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<text
|
||||
class="invoice-list__tag"
|
||||
:class="`invoice-list__tag--${item.type}`"
|
||||
>
|
||||
{{ typeLabel(item.type) }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="invoice-list__tax-no">
|
||||
{{ item.taxNo }}
|
||||
</text>
|
||||
<view class="invoice-list__card-footer">
|
||||
<view
|
||||
class="invoice-list__default"
|
||||
@tap="handleSetDefault(item)"
|
||||
>
|
||||
<view
|
||||
class="invoice-list__radio"
|
||||
:class="{ 'invoice-list__radio--checked': item.isDefault }"
|
||||
>
|
||||
<view
|
||||
v-if="item.isDefault"
|
||||
class="invoice-list__radio-dot"
|
||||
/>
|
||||
</view>
|
||||
<text class="invoice-list__default-text">
|
||||
默认抬头
|
||||
</text>
|
||||
</view>
|
||||
<view class="invoice-list__actions">
|
||||
<text
|
||||
class="invoice-list__action"
|
||||
@tap="handleEdit(item)"
|
||||
>
|
||||
编辑
|
||||
</text>
|
||||
<text
|
||||
class="invoice-list__action"
|
||||
@tap="handleDelete(item)"
|
||||
>
|
||||
删除
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="invoice-list__footer">
|
||||
<view
|
||||
class="invoice-list__add-btn"
|
||||
@tap="handleAdd"
|
||||
>
|
||||
+ 新增抬头
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<BottomSheet
|
||||
:visible="sheetVisible"
|
||||
@close="closeSheet"
|
||||
>
|
||||
<text class="invoice-list__sheet-title">
|
||||
{{ sheetTitle }}
|
||||
</text>
|
||||
|
||||
<view class="invoice-list__sheet-field">
|
||||
<text class="invoice-list__sheet-label">
|
||||
抬头类型
|
||||
</text>
|
||||
<view class="invoice-list__type-row">
|
||||
<view
|
||||
v-for="option in formTypeOptions"
|
||||
:key="option.value"
|
||||
class="invoice-list__type-option"
|
||||
@tap="handleSelectType(option.value)"
|
||||
>
|
||||
<view
|
||||
class="invoice-list__type-radio"
|
||||
:class="{ 'invoice-list__type-radio--checked': headerType === option.value }"
|
||||
>
|
||||
<text
|
||||
v-if="headerType === option.value"
|
||||
class="invoice-list__type-check"
|
||||
>
|
||||
✓
|
||||
</text>
|
||||
</view>
|
||||
<text class="invoice-list__type-label">
|
||||
{{ option.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="invoice-list__sheet-field">
|
||||
<text class="invoice-list__sheet-label">
|
||||
发票抬头
|
||||
</text>
|
||||
<input
|
||||
v-model="headerTitle"
|
||||
class="invoice-list__sheet-input"
|
||||
placeholder="请填写发票抬头"
|
||||
placeholder-class="invoice-list__sheet-placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="invoice-list__sheet-field">
|
||||
<text class="invoice-list__sheet-label">
|
||||
税号
|
||||
</text>
|
||||
<input
|
||||
v-model="taxNo"
|
||||
class="invoice-list__sheet-input"
|
||||
placeholder="请输入税号"
|
||||
placeholder-class="invoice-list__sheet-placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="invoice-list__sheet-submit"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
{{ sheetSubmitText }}
|
||||
</view>
|
||||
</BottomSheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
238
src/pages/personal-center/invoice/index.scss
Normal file
238
src/pages/personal-center/invoice/index.scss
Normal file
@@ -0,0 +1,238 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.invoice-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.invoice-page__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;
|
||||
}
|
||||
|
||||
.invoice-page__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.invoice-page__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.invoice-page__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.invoice-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.invoice-page__content {
|
||||
padding: 20rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.invoice-page__section {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.invoice-page__section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.invoice-page__section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__manage {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invoice-page__manage-text {
|
||||
font-size: 26rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.invoice-page__manage-arrow {
|
||||
margin-left: 4rpx;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.invoice-page__header-card {
|
||||
padding: 28rpx 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.invoice-page__header-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.invoice-page__default-tag {
|
||||
flex-shrink: 0;
|
||||
padding: 4rpx 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-stat-orange;
|
||||
background: $color-stat-orange-bg;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
.invoice-page__header-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 12rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__header-type {
|
||||
flex-shrink: 0;
|
||||
margin-left: 16rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
font-size: 22rpx;
|
||||
border-radius: 6rpx;
|
||||
|
||||
&--enterprise {
|
||||
color: $color-primary;
|
||||
border: 1rpx solid $color-primary;
|
||||
}
|
||||
|
||||
&--personal {
|
||||
color: $color-stat-orange;
|
||||
border: 1rpx solid $color-stat-orange;
|
||||
}
|
||||
}
|
||||
|
||||
.invoice-page__header-tax {
|
||||
display: block;
|
||||
margin-top: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #222222;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.invoice-page__search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 72rpx;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 36rpx;
|
||||
}
|
||||
|
||||
.invoice-page__search-icon {
|
||||
flex-shrink: 0;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.invoice-page__search-input {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__search-placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.invoice-page__tabs {
|
||||
display: flex;
|
||||
margin-top: 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.invoice-page__tab {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.invoice-page__tab-text {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.invoice-page__tab--active .invoice-page__tab-text {
|
||||
font-weight: 600;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.invoice-page__tab--active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 4rpx;
|
||||
background: $color-primary;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
|
||||
.invoice-page__record-card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-top: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.invoice-page__record-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.invoice-page__record-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__record-amount {
|
||||
flex-shrink: 0;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.invoice-page__record-row {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
175
src/pages/personal-center/invoice/index.vue
Normal file
175
src/pages/personal-center/invoice/index.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { INVOICE_RECORD_LIST, INVOICE_RECORD_TABS } from '@/constants/invoice'
|
||||
import { INVOICE_HEADER_TYPE_LABEL } from '@/constants/invoice-list'
|
||||
import { useInvoiceHeaders } from '@/hooks/use-invoice-headers'
|
||||
import type { InvoiceRecordStatus } from '@/types/invoice'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const { defaultHeader } = useInvoiceHeaders()
|
||||
|
||||
const keyword = ref('')
|
||||
const activeTab = ref<InvoiceRecordStatus>('all')
|
||||
const recordList = ref(INVOICE_RECORD_LIST.map(item => ({ ...item })))
|
||||
|
||||
const filteredRecords = computed(() => {
|
||||
const text = keyword.value.trim()
|
||||
|
||||
return recordList.value.filter((item) => {
|
||||
const matchTab = activeTab.value === 'all' || item.status === activeTab.value
|
||||
const matchKeyword = !text
|
||||
|| item.title.includes(text)
|
||||
|| item.relatedProject.includes(text)
|
||||
|| item.transactionNo.includes(text)
|
||||
|
||||
return matchTab && matchKeyword
|
||||
})
|
||||
})
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleManageHeader() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/personal-center/invoice-list/index',
|
||||
})
|
||||
}
|
||||
|
||||
function formatAmount(amount: number) {
|
||||
return `¥ ${amount}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="invoice-page">
|
||||
<view class="invoice-page__navbar">
|
||||
<view
|
||||
class="invoice-page__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="invoice-page__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="invoice-page__navbar-title">
|
||||
发票列表
|
||||
</text>
|
||||
<view class="invoice-page__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="invoice-page__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="invoice-page__content">
|
||||
<view class="invoice-page__section">
|
||||
<view class="invoice-page__section-header">
|
||||
<text class="invoice-page__section-title">
|
||||
默认发票抬头
|
||||
</text>
|
||||
<view
|
||||
class="invoice-page__manage"
|
||||
@tap="handleManageHeader"
|
||||
>
|
||||
<text class="invoice-page__manage-text">
|
||||
管理抬头
|
||||
</text>
|
||||
<text class="invoice-page__manage-arrow">
|
||||
>
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-if="defaultHeader"
|
||||
class="invoice-page__header-card"
|
||||
>
|
||||
<view class="invoice-page__header-top">
|
||||
<text class="invoice-page__default-tag">
|
||||
默认
|
||||
</text>
|
||||
<text class="invoice-page__header-title">
|
||||
{{ defaultHeader.title }}
|
||||
</text>
|
||||
<text
|
||||
class="invoice-page__header-type"
|
||||
:class="`invoice-page__header-type--${defaultHeader.type}`"
|
||||
>
|
||||
{{ INVOICE_HEADER_TYPE_LABEL[defaultHeader.type] }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="invoice-page__header-tax">
|
||||
{{ defaultHeader.taxNo }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="invoice-page__section">
|
||||
<text class="invoice-page__section-title">
|
||||
发票记录
|
||||
</text>
|
||||
|
||||
<view class="invoice-page__search">
|
||||
<image
|
||||
class="invoice-page__search-icon"
|
||||
src="/static/icons/icon-search.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<input
|
||||
v-model="keyword"
|
||||
class="invoice-page__search-input"
|
||||
placeholder="搜索案例标题、描述、标签..."
|
||||
placeholder-class="invoice-page__search-placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="invoice-page__tabs">
|
||||
<view
|
||||
v-for="tab in INVOICE_RECORD_TABS"
|
||||
:key="tab.value"
|
||||
class="invoice-page__tab"
|
||||
:class="{ 'invoice-page__tab--active': activeTab === tab.value }"
|
||||
@tap="activeTab = tab.value"
|
||||
>
|
||||
<text class="invoice-page__tab-text">
|
||||
{{ tab.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-for="item in filteredRecords"
|
||||
:key="item.id"
|
||||
class="invoice-page__record-card"
|
||||
>
|
||||
<view class="invoice-page__record-header">
|
||||
<text class="invoice-page__record-title">
|
||||
{{ item.title }} {{ item.ratio }}
|
||||
</text>
|
||||
<text class="invoice-page__record-amount">
|
||||
{{ formatAmount(item.amount) }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="invoice-page__record-row">
|
||||
交易流水号:{{ item.transactionNo }}
|
||||
</text>
|
||||
<text class="invoice-page__record-row">
|
||||
关联项目:{{ item.relatedProject }}
|
||||
</text>
|
||||
<text class="invoice-page__record-row">
|
||||
开票时间:{{ item.invoiceTime }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
131
src/pages/personal-center/qualification-list/index.scss
Normal file
131
src/pages/personal-center/qualification-list/index.scss
Normal file
@@ -0,0 +1,131 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.qualification-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.qualification-list__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;
|
||||
}
|
||||
|
||||
.qualification-list__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.qualification-list__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.qualification-list__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-list__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.qualification-list__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.qualification-list__content {
|
||||
padding: 20rpx 24rpx calc(140rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.qualification-list__card {
|
||||
padding: 28rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.qualification-list__card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.qualification-list__type {
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.qualification-list__status {
|
||||
font-size: 24rpx;
|
||||
color: $color-stat-orange;
|
||||
}
|
||||
|
||||
.qualification-list__name {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-list__row {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #222222;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.qualification-list__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 32rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.qualification-list__action {
|
||||
font-size: 26rpx;
|
||||
|
||||
&--edit {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
&--delete {
|
||||
color: #ef4444;
|
||||
}
|
||||
}
|
||||
|
||||
.qualification-list__footer {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.qualification-list__add-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;
|
||||
}
|
||||
131
src/pages/personal-center/qualification-list/index.vue
Normal file
131
src/pages/personal-center/qualification-list/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
import { QUALIFICATION_STATUS_LABEL } from '@/constants/qualification'
|
||||
import { useQualifications } from '@/hooks/use-qualifications'
|
||||
import type { QualificationItem } from '@/types/qualification'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const { qualifications } = useQualifications()
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function formatValidity(item: QualificationItem) {
|
||||
return `${item.validFrom}至${item.validTo}`
|
||||
}
|
||||
|
||||
function statusLabel(status: QualificationItem['status']) {
|
||||
return QUALIFICATION_STATUS_LABEL[status]
|
||||
}
|
||||
|
||||
function handleAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/personal-center/qualification-upload/index',
|
||||
})
|
||||
}
|
||||
|
||||
function handleEdit(item: QualificationItem) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/personal-center/qualification-upload/index?id=${encodeURIComponent(item.id)}`,
|
||||
})
|
||||
}
|
||||
|
||||
function handleDelete(item: QualificationItem) {
|
||||
uni.showModal({
|
||||
title: '删除资质',
|
||||
content: `确认删除「${item.certificateName}」吗?`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#2563EB',
|
||||
success: (res) => {
|
||||
if (!res.confirm)
|
||||
return
|
||||
|
||||
qualifications.value = qualifications.value.filter(row => row.id !== item.id)
|
||||
uni.showToast({ title: '已删除', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="qualification-list">
|
||||
<view class="qualification-list__navbar">
|
||||
<view
|
||||
class="qualification-list__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="qualification-list__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="qualification-list__navbar-title">
|
||||
资质列表
|
||||
</text>
|
||||
<view class="qualification-list__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="qualification-list__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="qualification-list__content">
|
||||
<view
|
||||
v-for="item in qualifications"
|
||||
:key="item.id"
|
||||
class="qualification-list__card"
|
||||
>
|
||||
<view class="qualification-list__card-header">
|
||||
<text class="qualification-list__type">
|
||||
{{ item.type }}
|
||||
</text>
|
||||
<text class="qualification-list__status">
|
||||
{{ statusLabel(item.status) }}
|
||||
</text>
|
||||
</view>
|
||||
<text class="qualification-list__name">
|
||||
{{ item.certificateName }}
|
||||
</text>
|
||||
<text class="qualification-list__row">
|
||||
{{ item.certificateNo }}
|
||||
</text>
|
||||
<text class="qualification-list__row">
|
||||
{{ item.issuer }}
|
||||
</text>
|
||||
<text class="qualification-list__row">
|
||||
{{ formatValidity(item) }}
|
||||
</text>
|
||||
<view class="qualification-list__actions">
|
||||
<text
|
||||
class="qualification-list__action qualification-list__action--edit"
|
||||
@tap="handleEdit(item)"
|
||||
>
|
||||
编辑
|
||||
</text>
|
||||
<text
|
||||
class="qualification-list__action qualification-list__action--delete"
|
||||
@tap="handleDelete(item)"
|
||||
>
|
||||
删除
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="qualification-list__footer">
|
||||
<view
|
||||
class="qualification-list__add-btn"
|
||||
@tap="handleAdd"
|
||||
>
|
||||
+ 新增资质
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
248
src/pages/personal-center/qualification-upload/index.scss
Normal file
248
src/pages/personal-center/qualification-upload/index.scss
Normal file
@@ -0,0 +1,248 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.qualification-upload {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.qualification-upload__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;
|
||||
}
|
||||
|
||||
.qualification-upload__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-upload__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 20rpx 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qualification-upload__card {
|
||||
padding: 28rpx 24rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__field {
|
||||
margin-bottom: 28rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.qualification-upload__label {
|
||||
display: block;
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-upload__required {
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-upload__label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__hint {
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.qualification-upload__input,
|
||||
.qualification-upload__picker {
|
||||
height: 80rpx;
|
||||
padding: 0 24rpx;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #e8eaef;
|
||||
border-radius: 12rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.qualification-upload__input {
|
||||
width: 100%;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-upload__picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.qualification-upload__picker-text {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
|
||||
&--placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
.qualification-upload__picker-arrow {
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.qualification-upload__placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
|
||||
.qualification-upload__date-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__date {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80rpx;
|
||||
background: $color-bg-page;
|
||||
border: 1rpx solid #e8eaef;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__date-text {
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
|
||||
&--placeholder {
|
||||
color: $color-text-placeholder;
|
||||
}
|
||||
}
|
||||
|
||||
.qualification-upload__date-sep {
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
padding: 10rpx 10rpx 0 0;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 160rpx;
|
||||
padding: 20rpx 16rpx;
|
||||
background: $color-bg-section;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-remove {
|
||||
position: absolute;
|
||||
top: -10rpx;
|
||||
right: -10rpx;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-remove-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-name {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: $color-text-primary;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.qualification-upload__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;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-add-icon {
|
||||
font-size: 40rpx;
|
||||
line-height: 1;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.qualification-upload__attachment-add-text {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.qualification-upload__footer {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.qualification-upload__footer-btn {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
384
src/pages/personal-center/qualification-upload/index.vue
Normal file
384
src/pages/personal-center/qualification-upload/index.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { QUALIFICATION_TYPE_OPTIONS } from '@/constants/qualification'
|
||||
import { useQualifications } from '@/hooks/use-qualifications'
|
||||
import type { QualificationItem, QualificationStatus } from '@/types/qualification'
|
||||
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 { qualifications, getQualificationById } = useQualifications()
|
||||
|
||||
const editingId = ref<string | null>(null)
|
||||
const typeOptions = QUALIFICATION_TYPE_OPTIONS
|
||||
const typeIndex = ref(-1)
|
||||
const certificateName = ref('')
|
||||
const certificateNo = ref('')
|
||||
const issuer = ref('')
|
||||
const validFrom = ref('')
|
||||
const validTo = ref('')
|
||||
|
||||
const attachments = ref([
|
||||
{ id: '1', name: '需求文档.pdf', icon: pdfIcon },
|
||||
{ id: '2', name: '系统....pdf', icon: pdfIcon },
|
||||
{ id: '3', name: '参考图.png', icon: pngIcon },
|
||||
])
|
||||
|
||||
const selectedType = computed(() => {
|
||||
if (typeIndex.value < 0)
|
||||
return ''
|
||||
return typeOptions[typeIndex.value]
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
const rawId = options?.id
|
||||
if (!rawId)
|
||||
return
|
||||
|
||||
const id = decodeURIComponent(String(rawId))
|
||||
const item = getQualificationById(id)
|
||||
if (!item)
|
||||
return
|
||||
|
||||
editingId.value = id
|
||||
const index = typeOptions.indexOf(item.type)
|
||||
typeIndex.value = index >= 0 ? index : 0
|
||||
certificateName.value = item.certificateName
|
||||
certificateNo.value = item.certificateNo
|
||||
issuer.value = item.issuer
|
||||
validFrom.value = item.validFrom
|
||||
validTo.value = item.validTo
|
||||
})
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/qualification-list/index')
|
||||
}
|
||||
|
||||
function handleTypeChange(event: { detail: { value: string } }) {
|
||||
typeIndex.value = Number(event.detail.value)
|
||||
}
|
||||
|
||||
function handleValidFromChange(event: { detail: { value: string } }) {
|
||||
validFrom.value = event.detail.value
|
||||
}
|
||||
|
||||
function handleValidToChange(event: { detail: { value: string } }) {
|
||||
validTo.value = event.detail.value
|
||||
}
|
||||
|
||||
function handleRemoveAttachment(id: string) {
|
||||
attachments.value = attachments.value.filter(item => item.id !== id)
|
||||
}
|
||||
|
||||
function handleAddAttachment() {
|
||||
uni.showToast({ title: '附件上传开发中', icon: 'none' })
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
if (!editingId.value) {
|
||||
typeIndex.value = -1
|
||||
certificateName.value = ''
|
||||
certificateNo.value = ''
|
||||
issuer.value = ''
|
||||
validFrom.value = ''
|
||||
validTo.value = ''
|
||||
}
|
||||
else {
|
||||
const item = getQualificationById(editingId.value)
|
||||
if (item) {
|
||||
const index = typeOptions.indexOf(item.type)
|
||||
typeIndex.value = index >= 0 ? index : 0
|
||||
certificateName.value = item.certificateName
|
||||
certificateNo.value = item.certificateNo
|
||||
issuer.value = item.issuer
|
||||
validFrom.value = item.validFrom
|
||||
validTo.value = item.validTo
|
||||
}
|
||||
}
|
||||
|
||||
attachments.value = [
|
||||
{ id: '1', name: '需求文档.pdf', icon: pdfIcon },
|
||||
{ id: '2', name: '系统....pdf', icon: pdfIcon },
|
||||
{ id: '3', name: '参考图.png', icon: pngIcon },
|
||||
]
|
||||
}
|
||||
|
||||
function validateForm() {
|
||||
if (!selectedType.value) {
|
||||
uni.showToast({ title: '请选择资质类型', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (!certificateName.value.trim()) {
|
||||
uni.showToast({ title: '请输入证书名称', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (!certificateNo.value.trim()) {
|
||||
uni.showToast({ title: '请输入证书编号', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (!issuer.value.trim()) {
|
||||
uni.showToast({ title: '请输入发证单位', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
if (!validFrom.value || !validTo.value) {
|
||||
uni.showToast({ title: '请选择有效期', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function buildPayload(status: QualificationStatus): Omit<QualificationItem, 'id'> {
|
||||
return {
|
||||
type: selectedType.value,
|
||||
certificateName: certificateName.value.trim(),
|
||||
certificateNo: certificateNo.value.trim(),
|
||||
issuer: issuer.value.trim(),
|
||||
validFrom: validFrom.value,
|
||||
validTo: validTo.value,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
function upsertItem(status: QualificationStatus) {
|
||||
const payload = buildPayload(status)
|
||||
|
||||
if (editingId.value) {
|
||||
qualifications.value = qualifications.value.map(row => (
|
||||
row.id === editingId.value
|
||||
? { ...row, ...payload }
|
||||
: row
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
qualifications.value.push({
|
||||
id: `${Date.now()}`,
|
||||
...payload,
|
||||
})
|
||||
}
|
||||
|
||||
function handleSaveDraft() {
|
||||
if (!validateForm())
|
||||
return
|
||||
|
||||
upsertItem('draft')
|
||||
uni.showToast({ title: '草稿已保存', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
if (!validateForm())
|
||||
return
|
||||
|
||||
upsertItem('reviewing')
|
||||
uni.showToast({ title: '提交成功', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1200)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="qualification-upload">
|
||||
<view class="qualification-upload__navbar">
|
||||
<view
|
||||
class="qualification-upload__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="qualification-upload__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="qualification-upload__navbar-title">
|
||||
资质上传
|
||||
</text>
|
||||
<view class="qualification-upload__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<scroll-view
|
||||
class="qualification-upload__scroll"
|
||||
scroll-y
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<view class="qualification-upload__card">
|
||||
<view class="qualification-upload__field">
|
||||
<text class="qualification-upload__label">
|
||||
资质类型<text class="qualification-upload__required">*</text>
|
||||
</text>
|
||||
<picker
|
||||
mode="selector"
|
||||
:range="typeOptions"
|
||||
:value="typeIndex < 0 ? 0 : typeIndex"
|
||||
@change="handleTypeChange"
|
||||
>
|
||||
<view class="qualification-upload__picker">
|
||||
<text
|
||||
class="qualification-upload__picker-text"
|
||||
:class="{ 'qualification-upload__picker-text--placeholder': !selectedType }"
|
||||
>
|
||||
{{ selectedType || '请选择资质类型' }}
|
||||
</text>
|
||||
<text class="qualification-upload__picker-arrow">
|
||||
▾
|
||||
</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="qualification-upload__field">
|
||||
<text class="qualification-upload__label">
|
||||
证书名称<text class="qualification-upload__required">*</text>
|
||||
</text>
|
||||
<input
|
||||
v-model="certificateName"
|
||||
class="qualification-upload__input"
|
||||
placeholder="请输入证书名称"
|
||||
placeholder-class="qualification-upload__placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="qualification-upload__field">
|
||||
<text class="qualification-upload__label">
|
||||
证书编号<text class="qualification-upload__required">*</text>
|
||||
</text>
|
||||
<input
|
||||
v-model="certificateNo"
|
||||
class="qualification-upload__input"
|
||||
placeholder="请输入证书编号"
|
||||
placeholder-class="qualification-upload__placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="qualification-upload__field">
|
||||
<text class="qualification-upload__label">
|
||||
发证单位<text class="qualification-upload__required">*</text>
|
||||
</text>
|
||||
<input
|
||||
v-model="issuer"
|
||||
class="qualification-upload__input"
|
||||
placeholder="请输入发证单位"
|
||||
placeholder-class="qualification-upload__placeholder"
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="qualification-upload__field">
|
||||
<text class="qualification-upload__label">
|
||||
有效期<text class="qualification-upload__required">*</text>
|
||||
</text>
|
||||
<view class="qualification-upload__date-row">
|
||||
<picker
|
||||
mode="date"
|
||||
:value="validFrom"
|
||||
@change="handleValidFromChange"
|
||||
>
|
||||
<view class="qualification-upload__date">
|
||||
<text
|
||||
class="qualification-upload__date-text"
|
||||
:class="{ 'qualification-upload__date-text--placeholder': !validFrom }"
|
||||
>
|
||||
{{ validFrom || '起始时间' }}
|
||||
</text>
|
||||
</view>
|
||||
</picker>
|
||||
<text class="qualification-upload__date-sep">
|
||||
至
|
||||
</text>
|
||||
<picker
|
||||
mode="date"
|
||||
:value="validTo"
|
||||
@change="handleValidToChange"
|
||||
>
|
||||
<view class="qualification-upload__date">
|
||||
<text
|
||||
class="qualification-upload__date-text"
|
||||
:class="{ 'qualification-upload__date-text--placeholder': !validTo }"
|
||||
>
|
||||
{{ validTo || '截止时间' }}
|
||||
</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="qualification-upload__field">
|
||||
<view class="qualification-upload__label-row">
|
||||
<text class="qualification-upload__label">
|
||||
附件上传
|
||||
</text>
|
||||
<text class="qualification-upload__hint">
|
||||
支持图片/文档/压缩包
|
||||
</text>
|
||||
</view>
|
||||
<view class="qualification-upload__attachment-row">
|
||||
<view
|
||||
v-for="file in attachments"
|
||||
:key="file.id"
|
||||
class="qualification-upload__attachment-item"
|
||||
>
|
||||
<view
|
||||
class="qualification-upload__attachment-remove"
|
||||
@tap="handleRemoveAttachment(file.id)"
|
||||
>
|
||||
<image
|
||||
class="qualification-upload__attachment-remove-icon"
|
||||
:src="deleteIcon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<image
|
||||
class="qualification-upload__attachment-icon"
|
||||
:src="file.icon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="qualification-upload__attachment-name">
|
||||
{{ file.name }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="qualification-upload__attachment-add"
|
||||
@tap="handleAddAttachment"
|
||||
>
|
||||
<text class="qualification-upload__attachment-add-icon">
|
||||
+
|
||||
</text>
|
||||
<text class="qualification-upload__attachment-add-text">
|
||||
添加附件
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="qualification-upload__footer">
|
||||
<view
|
||||
class="qualification-upload__footer-btn"
|
||||
@tap="handleSaveDraft"
|
||||
>
|
||||
保存草稿
|
||||
</view>
|
||||
<view
|
||||
class="qualification-upload__footer-btn"
|
||||
@tap="resetForm"
|
||||
>
|
||||
重置
|
||||
</view>
|
||||
<view
|
||||
class="qualification-upload__footer-btn"
|
||||
@tap="handleSubmit"
|
||||
>
|
||||
提交认证
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
94
src/pages/personal-center/service-hotline/index.scss
Normal file
94
src/pages/personal-center/service-hotline/index.scss
Normal file
@@ -0,0 +1,94 @@
|
||||
@import '@/styles/variables.scss';
|
||||
|
||||
.service-hotline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: $color-bg-section;
|
||||
}
|
||||
|
||||
.service-hotline__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;
|
||||
}
|
||||
|
||||
.service-hotline__back {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.service-hotline__back-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.service-hotline__navbar-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
}
|
||||
|
||||
.service-hotline__navbar-placeholder {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
}
|
||||
|
||||
.service-hotline__body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.service-hotline__card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 32rpx 28rpx;
|
||||
background: $color-bg-page;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.service-hotline__icon {
|
||||
flex-shrink: 0;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
}
|
||||
|
||||
.service-hotline__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.service-hotline__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text-primary;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.service-hotline__hours {
|
||||
display: block;
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.service-hotline__dial-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 88rpx;
|
||||
margin-top: 32rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
background: $color-primary;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
75
src/pages/personal-center/service-hotline/index.vue
Normal file
75
src/pages/personal-center/service-hotline/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
PERSONAL_CENTER_SERVICE_HOTLINE,
|
||||
PERSONAL_CENTER_SERVICE_HOTLINE_HOURS,
|
||||
} from '@/constants/personal-center'
|
||||
import serviceHotlineIcon from '@/static/icon/service-hotline.svg'
|
||||
import { navigateBackWithFallback } from '@/utils/navigate-back'
|
||||
|
||||
const hotlineDisplay = `专属客服热线-${PERSONAL_CENTER_SERVICE_HOTLINE}`
|
||||
const workHours = PERSONAL_CENTER_SERVICE_HOTLINE_HOURS
|
||||
const dialNumber = PERSONAL_CENTER_SERVICE_HOTLINE.replace(/-/g, '')
|
||||
|
||||
function handleBack() {
|
||||
navigateBackWithFallback('/pages/personal-center/index')
|
||||
}
|
||||
|
||||
function handleDial() {
|
||||
uni.makePhoneCall({
|
||||
phoneNumber: dialNumber,
|
||||
fail: () => {
|
||||
uni.showToast({ title: PERSONAL_CENTER_SERVICE_HOTLINE, icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="service-hotline">
|
||||
<view class="service-hotline__navbar">
|
||||
<view
|
||||
class="service-hotline__back"
|
||||
@tap="handleBack"
|
||||
>
|
||||
<image
|
||||
class="service-hotline__back-icon"
|
||||
src="/static/icons/icon-back.svg"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<text class="service-hotline__navbar-title">
|
||||
专属客服热线
|
||||
</text>
|
||||
<view class="service-hotline__navbar-placeholder" />
|
||||
</view>
|
||||
|
||||
<view class="service-hotline__body">
|
||||
<view class="service-hotline__card">
|
||||
<image
|
||||
class="service-hotline__icon"
|
||||
:src="serviceHotlineIcon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="service-hotline__info">
|
||||
<text class="service-hotline__title">
|
||||
{{ hotlineDisplay }}
|
||||
</text>
|
||||
<text class="service-hotline__hours">
|
||||
{{ workHours }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="service-hotline__dial-btn"
|
||||
@tap="handleDial"
|
||||
>
|
||||
点击拨号
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import './index.scss';
|
||||
</style>
|
||||
1
src/static/icon/service-hotline.svg
Normal file
1
src/static/icon/service-hotline.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="1789542083912" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13339" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M517.423 770.417c4.385 0 8.843-0.268 13.336-0.8 43.066-5.171 79.639-35.276 93.136-76.762 1.019-3.102-0.449-6.423-3.42-7.789-2.936-1.366-6.492-0.185-8.155 2.6-0.156 0.263-17.267 27.901-83.883 36.901-9.975 1.336-19.677 2.039-28.876 2.039-46.671-0.03-67.469-17.624-67.659-17.784-2.351-2.088-5.936-2.141-8.341-0.107-2.434 2.034-2.994 5.57-1.336 8.278 20.319 32.936 56.776 53.424 95.198 53.424z m358.196-412.861c-0.586 0-1.123 0.107-1.678 0.107-39.037-168.986-188.18-295.043-367-295.043-183.735 0-336.303 133.018-370.121 308.999-32.876 5.916-57.904 34.817-57.904 69.767v139.879c0 39.203 31.438 70.992 70.255 70.992 21.896 0 41.226-10.342 54.079-26.218 31.067 82.165 94.313 148.411 174.346 183.365 0.196-0.415 1.708-3.36 3.533-5.712 1.257-1.625 2.672-2.961 3.882-2.961 1.254 0 2.41 0.458 3.395 1.151-18.526-13.789-85.434-84.683-99.932-183.609-6.356-43.544 26.258-86.288 64.241-93.361 60.982-11.356 121.642-24.291 182.619-35.427 38.764-7.073 65.255-28.335 81.453-63.743 3.795-8.273 9.278-25.008 11.795-49.143 0.663-3.585 3.609-6.341 7.375-6.341 2.516 0 4.653 1.282 6.073 3.131l1.683-1.043c24.033 34.895 71.694 112.159 78.542 193.628 7.858 93.141 3.473 156.938-67.82 222.197a3.076 3.076 0 0 1-0.293 0.273 5.834 5.834 0 0 0-1.58 3.991c0 2.004 1.068 3.721 2.619 4.765 0.586 0.239 1.176 0.561 1.766 0.804 0.478 0.107 0.937 0.263 1.415 0.263 0.488 0 0.913-0.156 1.341-0.263 1.015-0.536 1.975-1.15 2.966-1.687 72.099-39.72 127.409-106.053 152.967-185.833 10.347 15.369 26.545 26.433 45.363 30.29-30.183 137.276-152.615 223.193-304.3 236.231-9.092-22.55-31.466-38.559-57.748-38.559-34.32 0-62.152 27.179-62.152 60.685 0 33.5 27.828 60.655 62.152 60.655 27.671 0 50.846-17.779 58.894-42.208 175.58-14.271 316.333-118.638 345.717-280.794 25.961-10.853 44.188-36.32 44.188-66.046V429.201c-0.004-39.559-32.3-71.645-72.131-71.645z m-64.773 40.656c-45.792-124.051-164.464-212.666-304.061-212.666-139.017 0-257.258 87.868-303.481 211.115-2.298-2.815-4.995-5.223-7.643-7.658 26.169-150.191 155.426-264.38 311.28-264.38 155.104 0 283.966 113.067 310.988 262.214-2.668 3.585-5.132 7.332-7.083 11.375z m-433.24 411.197h-0.005c-0.028 0.063-0.028 0.073 0.005 0z m0 0" fill="#2681FF" p-id="13340"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
8
src/types/address.ts
Normal file
8
src/types/address.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface AddressItem {
|
||||
id: string
|
||||
name: string
|
||||
phone: string
|
||||
region: string
|
||||
detail: string
|
||||
isDefault: boolean
|
||||
}
|
||||
23
src/types/contract.ts
Normal file
23
src/types/contract.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export type ContractTab = 'all' | 'pending' | 'signed'
|
||||
|
||||
export type ContractSignStatus = 'signed' | 'pending'
|
||||
|
||||
export type ContractBadgeType = 'signed' | 'in_progress' | 'pending_sign'
|
||||
|
||||
export interface ContractBadge {
|
||||
type: ContractBadgeType
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface ContractItem {
|
||||
id: string
|
||||
contractNo: string
|
||||
title: string
|
||||
type: string
|
||||
relatedLabel: string
|
||||
relatedValue: string
|
||||
signStatus: ContractSignStatus
|
||||
badges: ContractBadge[]
|
||||
showDownload: boolean
|
||||
showSign: boolean
|
||||
}
|
||||
9
src/types/invoice-list.ts
Normal file
9
src/types/invoice-list.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type InvoiceHeaderType = 'enterprise' | 'personal'
|
||||
|
||||
export interface InvoiceHeaderItem {
|
||||
id: string
|
||||
title: string
|
||||
type: InvoiceHeaderType
|
||||
taxNo: string
|
||||
isDefault: boolean
|
||||
}
|
||||
14
src/types/invoice.ts
Normal file
14
src/types/invoice.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export type InvoiceRecordStatus = 'all' | 'invoiced' | 'not_invoiced' | 'invoicing'
|
||||
|
||||
export type InvoiceRecordItemStatus = Exclude<InvoiceRecordStatus, 'all'>
|
||||
|
||||
export interface InvoiceRecordItem {
|
||||
id: string
|
||||
title: string
|
||||
ratio: string
|
||||
amount: number
|
||||
transactionNo: string
|
||||
relatedProject: string
|
||||
invoiceTime: string
|
||||
status: InvoiceRecordItemStatus
|
||||
}
|
||||
12
src/types/qualification.ts
Normal file
12
src/types/qualification.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export type QualificationStatus = 'reviewing' | 'approved' | 'rejected' | 'draft'
|
||||
|
||||
export interface QualificationItem {
|
||||
id: string
|
||||
type: string
|
||||
certificateName: string
|
||||
certificateNo: string
|
||||
issuer: string
|
||||
validFrom: string
|
||||
validTo: string
|
||||
status: QualificationStatus
|
||||
}
|
||||
39
src/utils/quick-entry-navigate.ts
Normal file
39
src/utils/quick-entry-navigate.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
const TAB_BAR_PATHS = new Set([
|
||||
'/pages/home-index/index',
|
||||
'/pages/case-library/index',
|
||||
'/pages/message-center/index',
|
||||
'/pages/project-center/index',
|
||||
'/pages/personal-center/index',
|
||||
])
|
||||
|
||||
const QUICK_ENTRY_ROUTES: Record<string, string> = {
|
||||
project: '/pages/project-center/index',
|
||||
report: '/pages/home-index/requirement-submit/index',
|
||||
service: '/pages/message-center/customer-service/index',
|
||||
contract: '/pages/contract-center/index',
|
||||
finance: '/pages/personal-center/finance/index',
|
||||
personal: '/pages/personal-center/index',
|
||||
message: '/pages/message-center/index',
|
||||
security: '/pages/personal-center/account-security/index',
|
||||
feedback: '/pages/personal-center/feedback/index',
|
||||
'contract-list': '/pages/contract-center/index',
|
||||
invoice: '/pages/personal-center/invoice/index',
|
||||
address: '/pages/personal-center/address/index',
|
||||
favorites: '/pages/case-library/favorites/index',
|
||||
qualification: '/pages/personal-center/qualification-list/index',
|
||||
}
|
||||
|
||||
export function navigateQuickEntry(id: string, label: string) {
|
||||
const path = QUICK_ENTRY_ROUTES[id]
|
||||
if (!path) {
|
||||
uni.showToast({ title: `${label}开发中`, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
if (TAB_BAR_PATHS.has(path)) {
|
||||
uni.redirectTo({ url: path })
|
||||
return
|
||||
}
|
||||
|
||||
uni.navigateTo({ url: path })
|
||||
}
|
||||
Reference in New Issue
Block a user