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 iconMessagePush from '@/static/icon/message-push.png'
|
||||||
import iconQualification from '@/static/icon/qualification.png'
|
import iconQualification from '@/static/icon/qualification.png'
|
||||||
import iconSecurity from '@/static/icon/security.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 = {
|
export const PERSONAL_CENTER_PROFILE: PersonalCenterProfile = {
|
||||||
nickname: '这里是昵称',
|
nickname: '这里是昵称',
|
||||||
@@ -23,15 +23,17 @@ export const PERSONAL_CENTER_MENU: PersonalCenterMenuItem[] = [
|
|||||||
{ key: 'security', label: '账号安全', icon: iconSecurity, path: '/pages/personal-center/account-security/index' },
|
{ key: 'security', label: '账号安全', icon: iconSecurity, path: '/pages/personal-center/account-security/index' },
|
||||||
{ key: 'message-push', label: '消息推送', icon: iconMessagePush },
|
{ key: 'message-push', label: '消息推送', icon: iconMessagePush },
|
||||||
{ key: 'feedback', label: '意见反馈', icon: iconFeedback, path: '/pages/personal-center/feedback/index' },
|
{ 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: 'finance', label: '财务中心', icon: iconFinance, path: '/pages/personal-center/finance/index' },
|
||||||
{ key: 'invoice', label: '发票列表', icon: iconInvoice },
|
{ key: 'invoice', label: '发票列表', icon: iconInvoice, path: '/pages/personal-center/invoice/index' },
|
||||||
{ key: 'address', label: '地址管理', icon: iconAddress },
|
{ key: 'address', label: '地址管理', icon: iconAddress, path: '/pages/personal-center/address/index' },
|
||||||
{ key: 'favorites', label: '收藏列表', icon: iconFavorites, path: '/pages/case-library/favorites/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: 'help', label: '帮助中心', icon: iconHelp },
|
||||||
{ key: 'about', label: '关于我们', icon: iconAbout },
|
{ 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 = '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": {
|
"style": {
|
||||||
"navigationStyle": "custom"
|
"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": {
|
"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 AppTabBar from '@/components/AppTabBar.vue'
|
||||||
import { useGreeting } from '@/hooks/use-greeting'
|
import { useGreeting } from '@/hooks/use-greeting'
|
||||||
import { useQuickEntries } from '@/hooks/use-quick-entries'
|
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 headerBg from '@/static/icons/1.png'
|
||||||
import softwareIllus from '@/static/icons/software.png'
|
import softwareIllus from '@/static/icons/software.png'
|
||||||
import hardwareIllus from '@/static/icons/hardware.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[] = [
|
const projects: ProjectItem[] = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -163,6 +168,7 @@ const projects: ProjectItem[] = [
|
|||||||
v-for="entry in pinnedEntries"
|
v-for="entry in pinnedEntries"
|
||||||
:key="entry.id"
|
:key="entry.id"
|
||||||
class="home__quick-item"
|
class="home__quick-item"
|
||||||
|
@tap="handleQuickEntryTap(entry)"
|
||||||
>
|
>
|
||||||
<image
|
<image
|
||||||
class="home__quick-icon"
|
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 {
|
import {
|
||||||
PERSONAL_CENTER_MENU,
|
PERSONAL_CENTER_MENU,
|
||||||
PERSONAL_CENTER_PROFILE,
|
PERSONAL_CENTER_PROFILE,
|
||||||
PERSONAL_CENTER_SERVICE_HOTLINE,
|
|
||||||
} from '@/constants/personal-center'
|
} from '@/constants/personal-center'
|
||||||
import type { PersonalCenterMenuItem } from '@/types/personal-center'
|
import type { PersonalCenterMenuItem } from '@/types/personal-center'
|
||||||
import editIcon from '@/static/icon/edit.svg'
|
import editIcon from '@/static/icon/edit.svg'
|
||||||
@@ -21,16 +20,6 @@ function handleMenuTap(item: PersonalCenterMenuItem) {
|
|||||||
return
|
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' })
|
uni.showToast({ title: '功能开发中', icon: 'none' })
|
||||||
}
|
}
|
||||||
</script>
|
</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